diff --git a/src/main/java/neatlogic/framework/asynchronization/threadlocal/InterceptorContext.java b/src/main/java/neatlogic/framework/asynchronization/threadlocal/InterceptorContext.java new file mode 100644 index 0000000000000000000000000000000000000000..ab8bb9df8078e626ca9d83f61684126322e65de5 --- /dev/null +++ b/src/main/java/neatlogic/framework/asynchronization/threadlocal/InterceptorContext.java @@ -0,0 +1,73 @@ +/* + * + * Copyright (C) 2025 TechSure Co., Ltd. All Rights Reserved. + * This file is part of the NeatLogic software. + * Licensed under the NeatLogic Sustainable Use License (NSUL), Version 4.x – 2025. + * You may use this file only in compliance with the License. + * See the LICENSE file distributed with this work for the full license text. + * 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. + * + */ + +package neatlogic.framework.asynchronization.threadlocal; + +import org.apache.ibatis.mapping.MappedStatement; + +import java.io.Serial; +import java.io.Serializable; + +public class InterceptorContext implements Serializable { + + private static final ThreadLocal instance = new ThreadLocal<>(); + @Serial + private static final long serialVersionUID = -5420998728515359636L; + + private MappedStatement mappedStatement; + + private Object parameter; + // 判断是否查询了数据库 + private Boolean queryFromDatabase; + + private InterceptorContext() { + + } + + public static InterceptorContext init() { + InterceptorContext interceptorContext = new InterceptorContext(); + instance.set(interceptorContext); + return instance.get(); + } + + public static InterceptorContext get() { + return instance.get(); + } + + public void release() { + instance.remove(); + } + + public MappedStatement getMappedStatement() { + return mappedStatement; + } + + public void setMappedStatement(MappedStatement mappedStatement) { + this.mappedStatement = mappedStatement; + } + + public Object getParameter() { + return parameter; + } + + public void setParameter(Object parameter) { + this.parameter = parameter; + } + + public Boolean getQueryFromDatabase() { + return queryFromDatabase; + } + + public void setQueryFromDatabase(Boolean queryFromDatabase) { + this.queryFromDatabase = queryFromDatabase; + } +} diff --git a/src/main/java/neatlogic/framework/dao/config/mybatis-config.xml b/src/main/java/neatlogic/framework/dao/config/mybatis-config.xml index ab4479288113786f6cc37ef43cd0452894bf33f4..72b4eca64975eae346ff7c3d6392db046836b428 100644 --- a/src/main/java/neatlogic/framework/dao/config/mybatis-config.xml +++ b/src/main/java/neatlogic/framework/dao/config/mybatis-config.xml @@ -41,10 +41,12 @@ along with this program. If not, see .--> + + diff --git a/src/main/java/neatlogic/framework/dao/plugin/DataSchemaInterceptor.java b/src/main/java/neatlogic/framework/dao/plugin/DataSchemaInterceptor.java index 61b93c4218a523b64078b31921e290dfa3470af0..0f41d240536f844538f9ab545d8371d778c1fbc1 100644 --- a/src/main/java/neatlogic/framework/dao/plugin/DataSchemaInterceptor.java +++ b/src/main/java/neatlogic/framework/dao/plugin/DataSchemaInterceptor.java @@ -12,6 +12,7 @@ package neatlogic.framework.dao.plugin; +import neatlogic.framework.asynchronization.threadlocal.InterceptorContext; import neatlogic.framework.asynchronization.threadlocal.TenantContext; import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.mapping.BoundSql; @@ -31,7 +32,10 @@ public class DataSchemaInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { - SqlCostInterceptor.QUERY_FROM_DATABASE_INSTANCE.set(true); + InterceptorContext interceptorContext = InterceptorContext.get(); + if (interceptorContext != null) { + interceptorContext.setQueryFromDatabase(true); + } StatementHandler statementHandler = (StatementHandler) invocation.getTarget(); BoundSql boundSql = statementHandler.getBoundSql(); diff --git a/src/main/java/neatlogic/framework/dao/plugin/ExecutingSQLInterceptor.java b/src/main/java/neatlogic/framework/dao/plugin/ExecutingSQLInterceptor.java new file mode 100644 index 0000000000000000000000000000000000000000..f01211b4c8a6c7cf600b1eab9507a89881234cd1 --- /dev/null +++ b/src/main/java/neatlogic/framework/dao/plugin/ExecutingSQLInterceptor.java @@ -0,0 +1,61 @@ +/* + * + * Copyright (C) 2025 TechSure Co., Ltd. All Rights Reserved. + * This file is part of the NeatLogic software. + * Licensed under the NeatLogic Sustainable Use License (NSUL), Version 4.x – 2025. + * You may use this file only in compliance with the License. + * See the LICENSE file distributed with this work for the full license text. + * 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. + * + */ + +package neatlogic.framework.dao.plugin; + +import neatlogic.framework.asynchronization.threadlocal.InterceptorContext; +import org.apache.ibatis.executor.statement.StatementHandler; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.plugin.Interceptor; +import org.apache.ibatis.plugin.Intercepts; +import org.apache.ibatis.plugin.Invocation; +import org.apache.ibatis.plugin.Signature; +import org.apache.ibatis.session.ResultHandler; + +import java.sql.Statement; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Intercepts({ + @Signature(type = StatementHandler.class, method = "batch", args = {Statement.class}), + @Signature(type = StatementHandler.class, method = "update", args = {Statement.class}), + @Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}), + @Signature(type = StatementHandler.class, method = "queryCursor", args = {Statement.class}), +}) +public class ExecutingSQLInterceptor implements Interceptor { + + private final static Map thread2ExecutingSQLMap = new ConcurrentHashMap<>(); + + public static Map getThread2ExecutingSQLMap() { + return new HashMap<>(thread2ExecutingSQLMap); + } + + public static void clearThread2ExecutingSQLMap() { + thread2ExecutingSQLMap.clear(); + } + + @Override + public Object intercept(Invocation invocation) throws Throwable { + try { + InterceptorContext interceptorContext = InterceptorContext.get(); + if (interceptorContext != null) { + MappedStatement mappedStatement = interceptorContext.getMappedStatement(); + String sqlId = mappedStatement.getId(); + thread2ExecutingSQLMap.put(Thread.currentThread().getName(), sqlId); + } + return invocation.proceed(); + } finally { + thread2ExecutingSQLMap.remove(Thread.currentThread().getName()); + } + } +} diff --git a/src/main/java/neatlogic/framework/dao/plugin/ModifyResultMapTypeHandlerInterceptor.java b/src/main/java/neatlogic/framework/dao/plugin/ModifyResultMapTypeHandlerInterceptor.java index c5dd0e73ab5ec42728c77611f1cf939f40e3d0e2..e3d95548931e7571ba8197abdb3bab0b4c543284 100644 --- a/src/main/java/neatlogic/framework/dao/plugin/ModifyResultMapTypeHandlerInterceptor.java +++ b/src/main/java/neatlogic/framework/dao/plugin/ModifyResultMapTypeHandlerInterceptor.java @@ -12,6 +12,7 @@ package neatlogic.framework.dao.plugin; +import neatlogic.framework.asynchronization.threadlocal.InterceptorContext; import org.apache.commons.collections4.CollectionUtils; import org.apache.ibatis.executor.resultset.ResultSetHandler; import org.apache.ibatis.mapping.MappedStatement; @@ -43,12 +44,15 @@ import java.util.List; public class ModifyResultMapTypeHandlerInterceptor implements Interceptor { Logger logger = LoggerFactory.getLogger(ModifyResultMapTypeHandlerInterceptor.class); - public static final ThreadLocal mappedStatementThreadLocal = new ThreadLocal<>(); @Override public Object intercept(Invocation invocation) throws Throwable { try { - MappedStatement mappedStatement = mappedStatementThreadLocal.get(); + MappedStatement mappedStatement = null; + InterceptorContext interceptorContext = InterceptorContext.get(); + if (interceptorContext != null) { + mappedStatement = interceptorContext.getMappedStatement(); + } if (mappedStatement != null) { Configuration configuration = mappedStatement.getConfiguration(); int resultMappingSize = 0; @@ -79,8 +83,6 @@ public class ModifyResultMapTypeHandlerInterceptor implements Interceptor { } } catch (Exception e) { logger.error(e.getMessage(), e); - } finally { - mappedStatementThreadLocal.remove(); } return invocation.proceed(); } diff --git a/src/main/java/neatlogic/framework/dao/plugin/SqlCostInterceptor.java b/src/main/java/neatlogic/framework/dao/plugin/SqlCostInterceptor.java index b5793d92a5bde472eaafd9e45607e0e1936a33d9..7cbffc1d54bc17a45ac2b5b8552dd84df2d257aa 100644 --- a/src/main/java/neatlogic/framework/dao/plugin/SqlCostInterceptor.java +++ b/src/main/java/neatlogic/framework/dao/plugin/SqlCostInterceptor.java @@ -12,6 +12,7 @@ package neatlogic.framework.dao.plugin; +import neatlogic.framework.asynchronization.threadlocal.InterceptorContext; import neatlogic.framework.asynchronization.threadlocal.RequestContext; import neatlogic.framework.asynchronization.threadlocal.TenantContext; import neatlogic.framework.asynchronization.threadlocal.UserContext; @@ -48,8 +49,6 @@ import java.util.regex.Matcher; }) public class SqlCostInterceptor implements Interceptor { Logger logger = LoggerFactory.getLogger(SqlCostInterceptor.class); - // 判断是否查询了数据库 - public static final ThreadLocal QUERY_FROM_DATABASE_INSTANCE = new ThreadLocal<>(); public static class SqlIdMap { private static final Set sqlSet = new HashSet<>(); @@ -101,7 +100,6 @@ public class SqlCostInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0]; - ModifyResultMapTypeHandlerInterceptor.mappedStatementThreadLocal.set(mappedStatement); long starttime = 0; SqlAuditVo sqlAuditVo = null; boolean hasCacheFirstLevel = false; @@ -150,43 +148,42 @@ public class SqlCostInterceptor implements Interceptor { } catch (Exception e) { logger.error(e.getMessage(), e); } - QUERY_FROM_DATABASE_INSTANCE.set(false); - try { - // 执行完上面的任务后,不改变原有的sql执行过程 - Object val = invocation.proceed(); - if (sqlAuditVo != null) { - if (QUERY_FROM_DATABASE_INSTANCE.get()) { - // sql语句被执行,没有使用到缓存 - sqlAuditVo.setUseCacheLevel(StringUtils.EMPTY); + InterceptorContext interceptorContext = InterceptorContext.get(); + if (interceptorContext != null) { + interceptorContext.setQueryFromDatabase(false); + } + // 执行完上面的任务后,不改变原有的sql执行过程 + Object val = invocation.proceed(); + if (sqlAuditVo != null) { + if (interceptorContext != null && interceptorContext.getQueryFromDatabase()) { + // sql语句被执行,没有使用到缓存 + sqlAuditVo.setUseCacheLevel(StringUtils.EMPTY); + } else { + if (hasCacheFirstLevel) { + sqlAuditVo.setUseCacheLevel("一级缓存"); } else { - if (hasCacheFirstLevel) { - sqlAuditVo.setUseCacheLevel("一级缓存"); - } else { - sqlAuditVo.setUseCacheLevel("二级缓存"); - } + sqlAuditVo.setUseCacheLevel("二级缓存"); } - sqlAuditVo.setTimeCost(System.currentTimeMillis() - starttime); - sqlAuditVo.setRunTime(new Date()); + } + sqlAuditVo.setTimeCost(System.currentTimeMillis() - starttime); + sqlAuditVo.setRunTime(new Date()); - if (val != null) { - if (val instanceof List) { - sqlAuditVo.setRecordCount(((List) val).size()); - } else { - sqlAuditVo.setRecordCount(1); - } - } - SqlAuditManager.addSqlAudit(sqlAuditVo); - RequestContext requestContext = RequestContext.get(); - if (requestContext != null) { - requestContext.addSqlAudit(sqlAuditVo); + if (val != null) { + if (val instanceof List) { + sqlAuditVo.setRecordCount(((List) val).size()); + } else { + sqlAuditVo.setRecordCount(1); } - //System.out.println("time cost:" + (System.currentTimeMillis() - starttime) + "ms"); - //System.out.println("###########################################################################"); } - return val; - } finally { - QUERY_FROM_DATABASE_INSTANCE.remove(); + SqlAuditManager.addSqlAudit(sqlAuditVo); + RequestContext requestContext = RequestContext.get(); + if (requestContext != null) { + requestContext.addSqlAudit(sqlAuditVo); + } + //System.out.println("time cost:" + (System.currentTimeMillis() - starttime) + "ms"); + //System.out.println("###########################################################################"); } + return val; } public static String getSql(MappedStatement mappedStatement, Object parameterObject) { diff --git a/src/main/java/neatlogic/framework/dao/plugin/ThreadLocalInterceptor.java b/src/main/java/neatlogic/framework/dao/plugin/ThreadLocalInterceptor.java new file mode 100644 index 0000000000000000000000000000000000000000..eae532ea43d31b0100168bf82a8b350c4b645783 --- /dev/null +++ b/src/main/java/neatlogic/framework/dao/plugin/ThreadLocalInterceptor.java @@ -0,0 +1,42 @@ +/* + * + * Copyright (C) 2025 TechSure Co., Ltd. All Rights Reserved. + * This file is part of the NeatLogic software. + * Licensed under the NeatLogic Sustainable Use License (NSUL), Version 4.x – 2025. + * You may use this file only in compliance with the License. + * See the LICENSE file distributed with this work for the full license text. + * 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. + * + */ + +package neatlogic.framework.dao.plugin; + +import neatlogic.framework.asynchronization.threadlocal.InterceptorContext; +import org.apache.ibatis.executor.Executor; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.plugin.Interceptor; +import org.apache.ibatis.plugin.Intercepts; +import org.apache.ibatis.plugin.Invocation; +import org.apache.ibatis.plugin.Signature; +import org.apache.ibatis.session.ResultHandler; +import org.apache.ibatis.session.RowBounds; + +@Intercepts({ + @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}), + @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}), +}) +public class ThreadLocalInterceptor implements Interceptor { + + @Override + public Object intercept(Invocation invocation) throws Throwable { + InterceptorContext interceptorContext = InterceptorContext.init(); + try { + interceptorContext.setMappedStatement((MappedStatement) invocation.getArgs()[0]); + interceptorContext.setParameter(invocation.getArgs()[1]); + return invocation.proceed(); + } finally { + interceptorContext.release(); + } + } +} diff --git a/src/main/java/neatlogic/framework/logback/logback-base.xml b/src/main/java/neatlogic/framework/logback/logback-base.xml index de34ead75976286707ed2ad7dcfe038e0997f497..e981a24666dd9e74c44ae4dadc0773c89d88545d 100644 --- a/src/main/java/neatlogic/framework/logback/logback-base.xml +++ b/src/main/java/neatlogic/framework/logback/logback-base.xml @@ -277,6 +277,36 @@ true + + ${log4j.home}/SQLTransientConnectionException.log + + ${log4j.home}/SQLTransientConnectionException.log.%i + 1 + 5 + + + ERROR + ACCEPT + DENY + + + 100MB + + + [%-5level]%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %logger{36}[%line] [%tenant] %requestUrl- %msg%n + + + + + + + 0 + 50 + + true + + ${log4j.home}/sqlTimeout.log @@ -357,6 +387,9 @@ + + + diff --git a/src/main/java/neatlogic/framework/store/mysql/NeatLogicBasicDataSource.java b/src/main/java/neatlogic/framework/store/mysql/NeatLogicBasicDataSource.java index 097435a98eecb9f0ace778a580937008ed63ca0e..6668c9e43f3ee7f5f43d6fa4bd36738846360437 100644 --- a/src/main/java/neatlogic/framework/store/mysql/NeatLogicBasicDataSource.java +++ b/src/main/java/neatlogic/framework/store/mysql/NeatLogicBasicDataSource.java @@ -12,24 +12,102 @@ package neatlogic.framework.store.mysql; +import com.alibaba.fastjson.JSON; import com.zaxxer.hikari.HikariDataSource; +import com.zaxxer.hikari.HikariPoolMXBean; import neatlogic.framework.asynchronization.threadlocal.UserContext; import neatlogic.framework.common.util.RC4Util; +import neatlogic.framework.dao.plugin.ExecutingSQLInterceptor; +import neatlogic.framework.dto.healthcheck.DataSourceInfoVo; +import neatlogic.framework.util.ThreadUtil; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.jdbc.CannotGetJdbcConnectionException; +import java.io.IOException; +import java.io.StringWriter; import java.sql.Connection; import java.sql.SQLException; +import java.sql.SQLTransientConnectionException; import java.sql.Statement; +import java.util.Map; import java.util.Objects; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; public class NeatLogicBasicDataSource extends HikariDataSource {//替换dbcp2的BasicDataSource - private final Logger logger = LoggerFactory.getLogger(NeatLogicBasicDataSource.class); + private static final Logger logger = LoggerFactory.getLogger(NeatLogicBasicDataSource.class); + + // 保存上次抛异常的时间毫秒数 + private final static AtomicLong lastThrowExceptionMillisecondsAtomicLong = new AtomicLong(0); + private final static AtomicInteger countAtomicInteger = new AtomicInteger(3); + /** + * 五分钟内只打印三次日志 + */ + private synchronized void audit() { + try { + DataSourceInfoVo dataSourceInfoVo = new DataSourceInfoVo(); + dataSourceInfoVo.setPoolName(this.getPoolName()); + HikariPoolMXBean hikariPoolMXBean = this.getHikariPoolMXBean(); + if (hikariPoolMXBean != null) { + dataSourceInfoVo.setIdleConnections(hikariPoolMXBean.getIdleConnections()); + dataSourceInfoVo.setActiveConnections(hikariPoolMXBean.getActiveConnections()); + dataSourceInfoVo.setThreadsAwaitingConnection(hikariPoolMXBean.getThreadsAwaitingConnection()); + dataSourceInfoVo.setTotalConnections(hikariPoolMXBean.getTotalConnections()); + } + Map thread2ExecutingSQLMap = ExecutingSQLInterceptor.getThread2ExecutingSQLMap(); + StringWriter writer = new StringWriter(); + ThreadUtil.dumpTraces(writer); + writer.write("=================正在执行的SQL语句有" + thread2ExecutingSQLMap.size() + "条================="); + writer.write(System.lineSeparator()); + for (Map.Entry entry : thread2ExecutingSQLMap.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + writer.write("[" + key + "] 线程正在执行 " + value); + writer.write(System.lineSeparator()); + } + writer.write("连接池信息: " + JSON.toJSONString(dataSourceInfoVo)); + Logger SQLTransientConnectionExceptionAuditLogger = LoggerFactory.getLogger("SQLTransientConnectionExceptionAudit"); + SQLTransientConnectionExceptionAuditLogger.error(writer.toString()); + } catch (IOException e) { + logger.error(e.getMessage(), e); + } + } @Override public Connection getConnection() throws SQLException { - Connection conn = super.getConnection(); + Connection conn = null; + try { + conn = super.getConnection(); + } catch (CannotGetJdbcConnectionException | SQLTransientConnectionException ex) { + // 五分钟内只打印三次日志 + boolean flag = false; + long currentTimeMillis = System.currentTimeMillis(); + long lastThrowExceptionMilliseconds = lastThrowExceptionMillisecondsAtomicLong.getAndUpdate(operand -> currentTimeMillis); + long interval = currentTimeMillis - lastThrowExceptionMilliseconds; + if (interval > TimeUnit.MINUTES.toMillis(5)) { + if (countAtomicInteger.compareAndSet(3, 0)) { + flag = true; + } + } else { + int count = countAtomicInteger.updateAndGet(operand -> { + if (operand < 3) { + return operand + 1; + } else { + return operand; + } + }); + if (count < 3) { + flag = true; + } + } + if (flag) { + audit(); + } + throw ex; + } conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); try (Statement statement = conn.createStatement()) { if (Objects.equals(DatasourceManager.getDatabaseId(), DatabaseVendor.MYSQL.getDatabaseId())) { diff --git a/src/main/java/neatlogic/framework/util/ThreadUtil.java b/src/main/java/neatlogic/framework/util/ThreadUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..7e4651baea42edc4a4b3637ef7cfc5bc8fbae2b7 --- /dev/null +++ b/src/main/java/neatlogic/framework/util/ThreadUtil.java @@ -0,0 +1,71 @@ +/* + * + * Copyright (C) 2025 TechSure Co., Ltd. All Rights Reserved. + * This file is part of the NeatLogic software. + * Licensed under the NeatLogic Sustainable Use License (NSUL), Version 4.x – 2025. + * You may use this file only in compliance with the License. + * See the LICENSE file distributed with this work for the full license text. + * 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. + * + */ + +package neatlogic.framework.util; + +import neatlogic.framework.asynchronization.threadlocal.RequestContext; +import neatlogic.framework.common.config.Config; +import org.apache.commons.lang3.StringUtils; + +import java.io.IOException; +import java.io.Writer; +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadInfo; +import java.lang.management.ThreadMXBean; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +public class ThreadUtil { + + public static void dumpTraces(Writer writer) throws IOException { + ThreadMXBean mxBean = ManagementFactory.getThreadMXBean(); + ThreadInfo[] threadInfos = mxBean.getThreadInfo(mxBean.getAllThreadIds(), 0); + Map threadInfoMap = new HashMap<>(); + for (ThreadInfo threadInfo : threadInfos) { + if (threadInfo != null) { + threadInfoMap.put(threadInfo.getThreadId(), threadInfo); + } + } + Map stacks = Thread.getAllStackTraces(); + long now = System.currentTimeMillis(); + String localAddr = StringUtils.EMPTY; + String url = StringUtils.EMPTY; + RequestContext requestContext = RequestContext.get(); + if (requestContext != null) { + if (requestContext.getRequest() != null && StringUtils.isNotBlank(requestContext.getRequest().getLocalAddr())) { + localAddr = requestContext.getRequest().getLocalAddr(); + } + if (StringUtils.isNotBlank(requestContext.getUrl())) { + url = requestContext.getUrl(); + } + } + writer.write("\n=================" + stacks.size() + " thread of " + localAddr + " at " + new SimpleDateFormat("yyyy/MM/dd HH:mm:ss z").format(new Date(now)) + " start.serverId is " + Config.SCHEDULE_SERVER_ID + "=================\n\n"); + for (Map.Entry entry : stacks.entrySet()) { + Thread thread = entry.getKey(); + writer.write("\"" + thread.getName() + "\" prio=" + thread.getPriority() + " tid=" + thread.getId() + " " + thread.getState() + " " + (thread.isDaemon() ? "deamon" : "worker")); + ThreadInfo threadInfo = threadInfoMap.get(thread.getId()); + if (threadInfo != null) { + writer.write(" native=" + threadInfo.isInNative() + ", suspended=" + threadInfo.isSuspended() + ", block=" + threadInfo.getBlockedCount() + ", wait=" + threadInfo.getWaitedCount()); + writer.write(" lock=" + threadInfo.getLockName() + " owned by " + threadInfo.getLockOwnerName() + " (" + threadInfo.getLockOwnerId() + "), cpu=" + (mxBean.getThreadCpuTime(threadInfo.getThreadId()) / 1000000L) + ", user=" + (mxBean.getThreadUserTime(threadInfo.getThreadId()) / 1000000L) + "\n"); + } + for (StackTraceElement element : entry.getValue()) { + writer.write("\t\t"); + writer.write(element.toString()); + writer.write("\n"); + } + writer.write("\n"); + } + writer.write("=================" + stacks.size() + " thread of " + url + " at " + new SimpleDateFormat("yyyy/MM/dd HH:mm:ss z").format(new Date(now)) + " end.=================\n\n"); + } +}