diff --git a/README_zh.md b/README_zh.md new file mode 100644 index 0000000..e7776d2 --- /dev/null +++ b/README_zh.md @@ -0,0 +1,20 @@ +# TProfiler + +TProfiler是一款代码性能分析工具。 + +作者:shutong.dy@taobao.com + +**重要提示:TProfiler 需要 Java 6™ 虚拟机环境运行。** + +TProfiler 旨在用于生产环境,可持续监控方法执行的时间和次数、生成方法热点、对象创建热点以及线程状态分析等数据,为排查系统性能瓶颈提供数据支持。 + +更多详细信息和使用文档,请参阅 [TProfiler Wiki](https://github.com/alibaba/TProfiler/wiki)。该Wiki包含更全面的中文文档,包括: + +* TProfiler介绍 +* TProfiler实现原理 +* TProfiler日志分析 +* TProfiler配置部署 + +## 贡献 + +TProfiler 的源代码使用 GPL version 2 许可证。我们欢迎各种贡献,如果您有任何想法、Bug报告或补丁,请随时提交。 diff --git a/src/main/java/com/taobao/profile/Manager.java b/src/main/java/com/taobao/profile/Manager.java index 4644152..b8974e0 100644 --- a/src/main/java/com/taobao/profile/Manager.java +++ b/src/main/java/com/taobao/profile/Manager.java @@ -232,6 +232,10 @@ public static void setRecordTime(int recordTime) { Manager.recordTime = recordTime; } + public ProfConfig getProfConfig() { + return profConfig; + } + /** * 设置包名过滤器 * diff --git a/src/main/java/com/taobao/profile/Profiler.java b/src/main/java/com/taobao/profile/Profiler.java index 33e693b..11e53ea 100644 --- a/src/main/java/com/taobao/profile/Profiler.java +++ b/src/main/java/com/taobao/profile/Profiler.java @@ -260,10 +260,10 @@ public static void start4Mysql(String host,int port,String db,String sql){ Object[] frameData = new Object[6]; frameData[0] = thrData.stackNum; frameData[1] = startTime; - frameData[2] = host; + frameData[2] = host != null ? host : ""; frameData[3] = port; - frameData[4] = db; - frameData[5] = sql; + frameData[4] = db != null ? db : ""; + frameData[5] = sql != null ? sql : ""; thrData.stackFrame.push(frameData); thrData.stackNum++; } catch (Exception e) { @@ -296,19 +296,19 @@ public static void end4Mysql(){ RecordSlowQuery record = new RecordSlowQuery(); Map map = new HashMap(); - map.put("host", (String) frameData[2]); - map.put("port", frameData[3].toString()); - map.put("db", (String) frameData[4]); - map.put("sql", (String) frameData[5]); + map.put("host", String.valueOf(frameData[2])); + map.put("port", String.valueOf(frameData[3])); + map.put("db", String.valueOf(frameData[4])); + map.put("sql", String.valueOf(frameData[5])); record.setRequestDesc(map); record.setUseTime(endTime - (Long) frameData[1]); record.setType("MYSQL"); StringBuilder sb = new StringBuilder(); sb.append("MYSQL"); - sb.append((String) frameData[2]); - sb.append(frameData[3].toString()); - sb.append((String) frameData[4]); + sb.append(String.valueOf(frameData[2])); + sb.append(String.valueOf(frameData[3])); + sb.append(String.valueOf(frameData[4])); if(!isNeedRecord(record.getUseTime())){ return; diff --git a/src/main/java/com/taobao/profile/client/TProfilerClient.java b/src/main/java/com/taobao/profile/client/TProfilerClient.java index 849ece8..2234a6e 100644 --- a/src/main/java/com/taobao/profile/client/TProfilerClient.java +++ b/src/main/java/com/taobao/profile/client/TProfilerClient.java @@ -158,19 +158,43 @@ private static String read(InputStream in) throws IOException { * @param args */ public static void main(String[] args) { - if (args.length != 3) { - System.err.println("Usage: "); + if (args.length < 3 || args.length > 4) { + System.err.println("Usage: [auth_token]"); return; } - int port = Integer.valueOf(args[1]); - if (args[2].toLowerCase().equals(Manager.START)) { - start(args[0], port); - } else if (args[2].toLowerCase().equals(Manager.STOP)) { - stop(args[0], port); - } else if (args[2].toLowerCase().equals(Manager.FLUSHMETHOD)) { - flushMethod(args[0], port); + + String serverIp = args[0]; + int port = Integer.valueOf(args[1]); + String commandName = args[2].toLowerCase(); + String authToken = null; + + if (args.length == 4) { + authToken = args[3]; + } + + String commandToSend = ""; + if (commandName.equals(Manager.START.toLowerCase())) { + commandToSend = Manager.START; + } else if (commandName.equals(Manager.STOP.toLowerCase())) { + commandToSend = Manager.STOP; + } else if (commandName.equals(Manager.FLUSHMETHOD.toLowerCase())) { + commandToSend = Manager.FLUSHMETHOD; + } else if (commandName.equals(Manager.STATUS.toLowerCase())) { + commandToSend = Manager.STATUS; + } else { + System.err.println("Unknown command: " + commandName); + return; + } + + String finalCommand = commandToSend; + if (authToken != null && !authToken.trim().isEmpty()) { + finalCommand = authToken + "@" + commandToSend; + } + + if (commandToSend.equals(Manager.STATUS)) { + System.out.println(getStatus(finalCommand, serverIp, port)); } else { - System.out.println(status(args[0], port)); + doSend(finalCommand, serverIp, port); } } } diff --git a/src/main/java/com/taobao/profile/config/ProfConfig.java b/src/main/java/com/taobao/profile/config/ProfConfig.java index 5a1d3ec..35b1657 100644 --- a/src/main/java/com/taobao/profile/config/ProfConfig.java +++ b/src/main/java/com/taobao/profile/config/ProfConfig.java @@ -119,6 +119,9 @@ public class ProfConfig { */ private int recordTime; + private String socketBindAddress; + private String socketAuthToken; + /** * 构造方法 */ @@ -184,9 +187,21 @@ private void extractDefaultProfile() throws IOException { for (int len = -1; (len = in.read(buffer)) != -1;){ out.write(buffer, 0, len); } - }finally{ - in.close(); - out.close(); + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException e) { + e.printStackTrace(); // Or a proper logger + } + } + if (out != null) { + try { + out.close(); + } catch (IOException e) { + e.printStackTrace(); // Or a proper logger + } + } } } @@ -232,6 +247,8 @@ private void loadConfig(Properties properties) throws VariableNotFoundException String debugMode = properties.getProperty("debugMode"); String port = properties.getProperty("port"); String recordTime = properties.getProperty("recordTime","-1"); + String socketBindAddressProp = properties.getProperty("profiler.socket.bindaddress"); + String socketAuthTokenProp = properties.getProperty("profiler.socket.authtoken"); setPort(port == null ? 50000 : Integer.valueOf(port)); setDebugMode("true".equalsIgnoreCase(debugMode == null ? null : debugMode.trim())); @@ -245,6 +262,8 @@ private void loadConfig(Properties properties) throws VariableNotFoundException setStartProfTime(startProfTime); setNeedNanoTime("true".equals(needNanoTime)); setIgnoreGetSetMethod("true".equals(ignoreGetSetMethod)); + setSocketBindAddress(socketBindAddressProp); + setSocketAuthToken(socketAuthTokenProp); if (eachProfUseTime == null) { setEachProfUseTime(5); } else { @@ -274,7 +293,7 @@ private void loadConfig(Properties properties) throws VariableNotFoundException * @return */ public String getStartProfTime() { - return startProfTime; + return startProfTime != null ? startProfTime : ""; } /** @@ -288,7 +307,7 @@ public void setStartProfTime(String startProfTime) { * @return */ public String getEndProfTime() { - return endProfTime; + return endProfTime != null ? endProfTime : ""; } /** @@ -302,7 +321,7 @@ public void setEndProfTime(String endProfTime) { * @return */ public String getLogFilePath() { - return logFilePath; + return logFilePath != null ? logFilePath : ""; } /** @@ -316,7 +335,7 @@ public void setLogFilePath(String logFilePath) { * @return the methodFilePath */ public String getMethodFilePath() { - return methodFilePath; + return methodFilePath != null ? methodFilePath : ""; } /** @@ -331,7 +350,7 @@ public void setMethodFilePath(String methodFilePath) { * @return */ public String getIncludePackageStartsWith() { - return includePackageStartsWith; + return includePackageStartsWith != null ? includePackageStartsWith : ""; } /** @@ -373,7 +392,7 @@ public void setEachProfIntervalTime(int eachProfIntervalTime) { * @return */ public String getExcludePackageStartsWith() { - return excludePackageStartsWith; + return excludePackageStartsWith != null ? excludePackageStartsWith : ""; } /** @@ -431,7 +450,7 @@ public void setSamplerIntervalTime(int samplerIntervalTime) { * @return the samplerFilePath */ public String getSamplerFilePath() { - return samplerFilePath; + return samplerFilePath != null ? samplerFilePath : ""; } /** @@ -445,7 +464,7 @@ public int getSamplerIntervalTime() { * @return the excludeClassLoader */ public String getExcludeClassLoader() { - return excludeClassLoader; + return excludeClassLoader != null ? excludeClassLoader : ""; } /** @@ -484,4 +503,23 @@ public int getRecordTime() { public void setRecordTime(int recordTime) { this.recordTime = recordTime; } + + public String getSocketBindAddress() { + if (socketBindAddress == null || socketBindAddress.trim().isEmpty()) { + return "127.0.0.1"; // Default to localhost + } + return socketBindAddress; + } + + public void setSocketBindAddress(String socketBindAddress) { + this.socketBindAddress = socketBindAddress; + } + + public String getSocketAuthToken() { + return socketAuthToken; // Can be null or empty if not set + } + + public void setSocketAuthToken(String socketAuthToken) { + this.socketAuthToken = socketAuthToken; + } } diff --git a/src/main/java/com/taobao/profile/thread/DataDumpThread.java b/src/main/java/com/taobao/profile/thread/DataDumpThread.java index 647d65f..baa9e14 100644 --- a/src/main/java/com/taobao/profile/thread/DataDumpThread.java +++ b/src/main/java/com/taobao/profile/thread/DataDumpThread.java @@ -52,9 +52,40 @@ public class DataDumpThread extends Thread { */ public DataDumpThread(ProfConfig config) { // 读取用户配置 - fileWriter = new DailyRollingFileWriter(config.getLogFilePath()); - File temp = new File(config.getLogFilePath()); - mysqlFileWriter = new DailyRollingFileWriter(temp.getParent()+"/mysqlProfiler.log"); + String logFilePath = config.getLogFilePath(); // Already non-null due to ProfConfig changes (returns "") + // Initialize fileWriter (assuming DailyRollingFileWriter can handle empty string path or throw error) + try { + fileWriter = new DailyRollingFileWriter(logFilePath); + } catch (Exception e) { + System.err.println("TProfiler: Failed to initialize fileWriter for path: " + logFilePath); + e.printStackTrace(); + fileWriter = null; // Set to null if init fails + } + + String mysqlLogPath; + if (logFilePath.isEmpty()) { + // If main log path is empty (not configured), mysql log also goes to a default name in current dir + mysqlLogPath = "mysqlProfiler.log"; + System.err.println("TProfiler: logFilePath is empty. MySQL logs will be in current dir: " + new File(mysqlLogPath).getAbsolutePath()); + } else { + File temp = new File(logFilePath); + String parentDir = temp.getParent(); + if (parentDir == null) { + // logFilePath is a relative filename like "profiler.log" + mysqlLogPath = "mysqlProfiler.log"; + System.err.println("TProfiler: logFilePath has no parent directory. MySQL logs will be in current dir: " + new File(mysqlLogPath).getAbsolutePath()); + } else { + mysqlLogPath = new File(parentDir, "mysqlProfiler.log").getPath(); + } + } + + try { + mysqlFileWriter = new DailyRollingFileWriter(mysqlLogPath); + } catch (Exception e) { + System.err.println("TProfiler: Failed to initialize mysqlFileWriter for path: " + mysqlLogPath); + e.printStackTrace(); + mysqlFileWriter = null; // Ensure it's null if initialization fails + } eachProfUseTime = config.getEachProfUseTime(); eachProfIntervalTime = config.getEachProfIntervalTime(); } @@ -86,6 +117,9 @@ public void run() { if (fileWriter != null) { fileWriter.closeFile(); } + if (mysqlFileWriter != null) { + mysqlFileWriter.closeFile(); + } // 等待已开始的End方法执行完成 try { TimeUnit.MILLISECONDS.sleep(500L); @@ -102,6 +136,7 @@ public void run() { * @return */ private void dumpProfileData() { + if (fileWriter == null) return; ThreadData[] threadData = Profiler.threadProfile; for (int index = 0; index < threadData.length; index++) { ThreadData profilerData = threadData[index]; @@ -137,6 +172,7 @@ private void dumpProfileData() { * 记录Mysql方法的日志 */ private void dumpMysqlData(){ + if (mysqlFileWriter == null) return; SlowQueryData[] threadData = Profiler.slowQueryProfile; for (int index = 0; index < threadData.length; index++) { diff --git a/src/main/java/com/taobao/profile/thread/InnerSocketThread.java b/src/main/java/com/taobao/profile/thread/InnerSocketThread.java index afb6722..3c25f64 100644 --- a/src/main/java/com/taobao/profile/thread/InnerSocketThread.java +++ b/src/main/java/com/taobao/profile/thread/InnerSocketThread.java @@ -13,6 +13,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; @@ -27,6 +28,7 @@ * @since 2012-1-11 */ public class InnerSocketThread extends Thread { + private static final int MAX_COMMAND_LENGTH = 1024; /** * server */ @@ -39,19 +41,56 @@ public class InnerSocketThread extends Thread { */ public void run() { try { - socket = new ServerSocket(Manager.PORT); + com.taobao.profile.config.ProfConfig config = Manager.instance().getProfConfig(); + String bindAddress = config.getSocketBindAddress(); // Fetches from ProfConfig + int port = Manager.PORT; // Manager.PORT is already available via ProfConfig during Manager init + + try { + socket = new ServerSocket(port, 50, InetAddress.getByName(bindAddress)); + System.out.println("TProfiler: InnerSocketThread listening on " + bindAddress + ":" + port); + } catch (java.net.UnknownHostException e) { + System.err.println("TProfiler: InnerSocketThread could not bind to address " + bindAddress + ". Defaulting to all interfaces."); + e.printStackTrace(); // Log the error + socket = new ServerSocket(port); // Fallback to original behavior + } while (true) { Socket child = socket.accept(); child.setSoTimeout(5000); - String command = read(child.getInputStream()); + String rawFullCommand = read(child.getInputStream()); + + String authToken = config.getSocketAuthToken(); + String actualCommand = rawFullCommand; + boolean authenticated = false; + + if (authToken != null && !authToken.trim().isEmpty()) { + if (rawFullCommand != null && rawFullCommand.contains("@")) { + String[] parts = rawFullCommand.split("@", 2); + if (parts.length == 2 && authToken.equals(parts[0])) { + actualCommand = parts[1]; + authenticated = true; + } + } + if (!authenticated) { + System.err.println("TProfiler: InnerSocketThread authentication failed. Closing connection."); + // Optionally send an error response to client before closing + // child.getOutputStream().write("Authentication failed\r\n".getBytes()); + // child.getOutputStream().flush(); + child.close(); + continue; // Skip further processing for this connection + } + } else { + // No token configured, or token is empty string, so command is considered authenticated + authenticated = true; + } - if (Manager.START.equals(command)) { + // IMPORTANT: Use 'actualCommand' for all subsequent command comparisons, not 'rawFullCommand' + if (Manager.START.equals(actualCommand)) { Manager.instance().setSwitchFlag(true); - } else if (Manager.STATUS.equals(command)) { + } else if (Manager.STATUS.equals(actualCommand)) { write(child.getOutputStream()); - } else if (Manager.FLUSHMETHOD.equals(command)) { + } else if (Manager.FLUSHMETHOD.equals(actualCommand)) { MethodCache.flushMethodData(); } else { Manager.instance().setSwitchFlag(false); @@ -59,8 +98,14 @@ public void run() { child.close(); } } catch (SocketException e) { + // SocketException can occur if the socket is closed abruptly, + // or if there are network issues. + System.err.println("TProfiler: InnerSocketThread SocketException: " + e.getMessage()); e.printStackTrace(); } catch (IOException e) { + // This will catch the "Command exceeded maximum length" IOException + // as well as other general IO errors. + System.err.println("TProfiler: InnerSocketThread IOException: " + e.getMessage()); e.printStackTrace(); } finally { if (socket != null) { @@ -89,6 +134,9 @@ private String read(InputStream in) throws IOException { if (c == '\r') { break; } else { + if (sb.length() >= MAX_COMMAND_LENGTH) { + throw new IOException("Command exceeded maximum length of " + MAX_COMMAND_LENGTH + " bytes."); + } sb.append(c); } } diff --git a/src/main/java/com/taobao/profile/utils/DailyRollingFileWriter.java b/src/main/java/com/taobao/profile/utils/DailyRollingFileWriter.java index 97b61c2..17feb69 100644 --- a/src/main/java/com/taobao/profile/utils/DailyRollingFileWriter.java +++ b/src/main/java/com/taobao/profile/utils/DailyRollingFileWriter.java @@ -174,10 +174,26 @@ private void rolling(Date now) { * @param append */ private void createWriter(String filename, boolean append) { + FileWriter fw = null; try { - bufferedWriter = new BufferedWriter(new FileWriter(filename, append), 8 * 1024); + fw = new FileWriter(filename, append); + bufferedWriter = new BufferedWriter(fw, 8 * 1024); } catch (IOException e) { - e.printStackTrace(); + e.printStackTrace(); // Or a proper logger + if (fw != null) { + try { + fw.close(); + } catch (IOException ioe) { + ioe.printStackTrace(); // Or a proper logger + } + } + // Depending on desired error handling, you might want to set bufferedWriter to null + // or rethrow the exception to signal that writer creation failed. + // For now, this just logs and ensures fw is closed if it was opened. + // To ensure bufferedWriter is not used if it failed, you could add: + // this.bufferedWriter = null; + // Or ensure the caller checks if bufferedWriter is null if this method can fail silently. + // However, the original code also allowed bufferedWriter to remain unassigned on exception. } } @@ -186,15 +202,25 @@ private void createWriter(String filename, boolean append) { * @param file */ private void createWriter(File file) { + FileWriter fw = null; try { file = file.getCanonicalFile(); File parent = file.getParentFile(); if (parent != null && !parent.exists()) { parent.mkdirs(); } - bufferedWriter = new BufferedWriter(new FileWriter(file), 8 * 1024); + fw = new FileWriter(file); // FileWriter(File) constructor overwrites by default + bufferedWriter = new BufferedWriter(fw, 8 * 1024); } catch (IOException e) { - e.printStackTrace(); + e.printStackTrace(); // Or a proper logger + if (fw != null) { + try { + fw.close(); + } catch (IOException ioe) { + ioe.printStackTrace(); // Or a proper logger + } + } + // Similar error handling considerations as above for bufferedWriter state. } }