/* * (C) Copyright 2011 Nuxeo SA (http://nuxeo.com/) and others. * * 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. * * Contributors: * matic */ package org.nuxeo.ecm.core.management.jtajca.internal; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.naming.NamingException; import javax.transaction.Status; import javax.transaction.Synchronization; import javax.transaction.Transaction; import javax.transaction.TransactionManager; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.geronimo.transaction.manager.TransactionImpl; import org.apache.geronimo.transaction.manager.TransactionManagerImpl; import org.apache.geronimo.transaction.manager.TransactionManagerMonitor; import org.apache.geronimo.transaction.manager.XidImpl; import org.apache.log4j.MDC; import org.javasimon.SimonManager; import org.javasimon.Stopwatch; import org.nuxeo.ecm.core.management.jtajca.TransactionMonitor; import org.nuxeo.ecm.core.management.jtajca.TransactionStatistics; import org.nuxeo.ecm.core.management.jtajca.internal.DefaultMonitorComponent.ServerInstance; import org.nuxeo.runtime.jtajca.NuxeoContainer; import org.nuxeo.runtime.transaction.TransactionHelper; /** * @author matic */ public class DefaultTransactionMonitor implements TransactionManagerMonitor, TransactionMonitor, Synchronization { protected static final Log log = LogFactory.getLog(DefaultTransactionMonitor.class); protected TransactionManagerImpl tm; protected boolean enabled; @Override public void install() { tm = lookup(); if (tm == null) { log.warn("Cannot monitor transactions, not a geronimo tx manager"); return; } bindManagementInterface(); toggle(); } @Override public void uninstall() { if (tm == null) { return; } unbindManagementInterface(); if (enabled) { toggle(); } } protected ServerInstance self; protected void bindManagementInterface() { self = DefaultMonitorComponent.bind(TransactionMonitor.class, this); } protected void unbindManagementInterface() { DefaultMonitorComponent.unbind(self); self = null; } protected TransactionManagerImpl lookup() { TransactionManager tm = NuxeoContainer.getTransactionManager(); if (tm == null) { // try setup trough NuxeoTransactionManagerFactory try { tm = TransactionHelper.lookupTransactionManager(); } catch (NamingException cause) { throw new RuntimeException("Cannot lookup tx manager", cause); } } if (!(tm instanceof TransactionManagerImpl)) { return null; } return (TransactionManagerImpl) tm; } protected TransactionStatistics lastCommittedStatistics; protected TransactionStatistics lastRollbackedStatistics; protected final Map<Object, DefaultTransactionStatistics> activeStatistics = new HashMap<>(); public static String id(Object key) { if (key instanceof XidImpl) { byte[] globalId = ((XidImpl) key).getGlobalTransactionId(); StringBuffer buffer = new StringBuffer(); for (int i = 0; i < globalId.length; i++) { buffer.append(Integer.toHexString(globalId[i])); } return buffer.toString().replaceAll("0*$", ""); } return key.toString(); } public static String id(Transaction tx) { return Integer.toHexString(tx.hashCode()); } @Override public void threadAssociated(Transaction tx) { long now = System.currentTimeMillis(); Object key = tm.getTransactionKey(); MDC.put("tx", id(key)); Stopwatch sw = SimonManager.getStopwatch("tx"); final Thread thread = Thread.currentThread(); DefaultTransactionStatistics info = new DefaultTransactionStatistics(key); info.split = sw.start(); info.threadName = thread.getName(); info.status = TransactionStatistics.Status.fromTx(tx); info.startTimestamp = now; info.startCapturedContext = new Throwable("** start invoke context **"); synchronized (this) { activeStatistics.put(key, info); } if (TransactionStatistics.Status.ACTIVE == info.status) { tm.registerInterposedSynchronization(this); // register end status } if (log.isTraceEnabled()) { log.trace(info.toString()); } } @Override public void threadUnassociated(Transaction tx) { try { Object key = ((TransactionImpl) tx).getTransactionKey(); DefaultTransactionStatistics stats; synchronized (DefaultTransactionMonitor.class) { stats = activeStatistics.remove(key); } if (stats == null) { log.debug(key + " not found in active statistics map"); return; } stats.split.stop(); stats.split = null; if (log.isTraceEnabled()) { log.trace(stats); } if (TransactionStatistics.Status.COMMITTED.equals(stats.status)) { lastCommittedStatistics = stats; } else if (TransactionStatistics.Status.ROLLEDBACK.equals(stats.status)) { lastRollbackedStatistics = stats; } } finally { MDC.remove("tx"); } } @Override public List<TransactionStatistics> getActiveStatistics() { List<TransactionStatistics> l = new ArrayList<>(activeStatistics.values()); Collections.sort(l, new Comparator<TransactionStatistics>() { @Override public int compare(TransactionStatistics o1, TransactionStatistics o2) { return o1.getStartDate().compareTo(o2.getEndDate()); } }); return l; } @Override public long getActiveCount() { return tm.getActiveCount(); } @Override public long getTotalCommits() { return tm.getTotalCommits(); } @Override public long getTotalRollbacks() { return tm.getTotalRollbacks(); } @Override public TransactionStatistics getLastCommittedStatistics() { return lastCommittedStatistics; } @Override public TransactionStatistics getLastRollbackedStatistics() { return lastRollbackedStatistics; } protected DefaultTransactionStatistics thisStatistics() { Object key = tm.getTransactionKey(); DefaultTransactionStatistics stats; synchronized (this) { stats = activeStatistics.get(key); } if (stats == null) { log.debug(key + " not found in active statistics map"); } return stats; } @Override public void beforeCompletion() { DefaultTransactionStatistics stats = thisStatistics(); if (stats == null) { return; } stats.endCapturedContext = new Throwable("** end invoke context **"); } @Override public void afterCompletion(int code) { DefaultTransactionStatistics stats = thisStatistics(); if (stats == null) { return; } stats.endTimestamp = System.currentTimeMillis(); stats.status = TransactionStatistics.Status.fromCode(code); switch (code) { case Status.STATUS_COMMITTED: lastCommittedStatistics = stats; break; case Status.STATUS_ROLLEDBACK: lastRollbackedStatistics = stats; stats.endCapturedContext = new Throwable("** rollback context **"); break; } } @Override public boolean toggle() { if (enabled) { tm.removeTransactionAssociationListener(this); activeStatistics.clear(); enabled = false; } else { tm.addTransactionAssociationListener(this); enabled = true; } return enabled; } @Override public boolean getEnabled() { return enabled; } }