001/* 002 * (C) Copyright 2011 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * matic 018 */ 019package org.nuxeo.ecm.core.management.jtajca.internal; 020 021import java.util.ArrayList; 022import java.util.Collections; 023import java.util.Comparator; 024import java.util.HashMap; 025import java.util.List; 026import java.util.Map; 027 028import javax.naming.NamingException; 029import javax.transaction.Status; 030import javax.transaction.Synchronization; 031import javax.transaction.Transaction; 032import javax.transaction.TransactionManager; 033 034import org.apache.commons.logging.Log; 035import org.apache.commons.logging.LogFactory; 036import org.apache.geronimo.transaction.manager.TransactionImpl; 037import org.apache.geronimo.transaction.manager.TransactionManagerImpl; 038import org.apache.geronimo.transaction.manager.TransactionManagerMonitor; 039import org.apache.geronimo.transaction.manager.XidImpl; 040import org.apache.log4j.MDC; 041import org.javasimon.SimonManager; 042import org.javasimon.Stopwatch; 043import org.nuxeo.ecm.core.management.jtajca.TransactionMonitor; 044import org.nuxeo.ecm.core.management.jtajca.TransactionStatistics; 045import org.nuxeo.ecm.core.management.jtajca.internal.DefaultMonitorComponent.ServerInstance; 046import org.nuxeo.runtime.jtajca.NuxeoContainer; 047import org.nuxeo.runtime.transaction.TransactionHelper; 048 049/** 050 * @author matic 051 */ 052public class DefaultTransactionMonitor implements TransactionManagerMonitor, TransactionMonitor, Synchronization { 053 054 protected static final Log log = LogFactory.getLog(DefaultTransactionMonitor.class); 055 056 protected TransactionManagerImpl tm; 057 058 protected boolean enabled; 059 060 @Override 061 public void install() { 062 tm = lookup(); 063 if (tm == null) { 064 log.warn("Cannot monitor transactions, not a geronimo tx manager"); 065 return; 066 } 067 bindManagementInterface(); 068 toggle(); 069 } 070 071 @Override 072 public void uninstall() { 073 if (tm == null) { 074 return; 075 } 076 unbindManagementInterface(); 077 if (enabled) { 078 toggle(); 079 } 080 } 081 082 protected ServerInstance self; 083 084 protected void bindManagementInterface() { 085 self = DefaultMonitorComponent.bind(TransactionMonitor.class, this); 086 } 087 088 protected void unbindManagementInterface() { 089 DefaultMonitorComponent.unbind(self); 090 self = null; 091 } 092 093 protected TransactionManagerImpl lookup() { 094 TransactionManager tm = NuxeoContainer.getTransactionManager(); 095 if (tm == null) { // try setup trough NuxeoTransactionManagerFactory 096 try { 097 tm = TransactionHelper.lookupTransactionManager(); 098 } catch (NamingException cause) { 099 throw new RuntimeException("Cannot lookup tx manager", cause); 100 } 101 } 102 if (!(tm instanceof TransactionManagerImpl)) { 103 return null; 104 } 105 return (TransactionManagerImpl) tm; 106 } 107 108 protected TransactionStatistics lastCommittedStatistics; 109 110 protected TransactionStatistics lastRollbackedStatistics; 111 112 protected final Map<Object, DefaultTransactionStatistics> activeStatistics = new HashMap<>(); 113 114 public static String id(Object key) { 115 if (key instanceof XidImpl) { 116 byte[] globalId = ((XidImpl) key).getGlobalTransactionId(); 117 StringBuffer buffer = new StringBuffer(); 118 for (int i = 0; i < globalId.length; i++) { 119 buffer.append(Integer.toHexString(globalId[i])); 120 } 121 return buffer.toString().replaceAll("0*$", ""); 122 } 123 return key.toString(); 124 } 125 126 public static String id(Transaction tx) { 127 return Integer.toHexString(tx.hashCode()); 128 } 129 130 @Override 131 public void threadAssociated(Transaction tx) { 132 long now = System.currentTimeMillis(); 133 Object key = tm.getTransactionKey(); 134 MDC.put("tx", id(key)); 135 Stopwatch sw = SimonManager.getStopwatch("tx"); 136 final Thread thread = Thread.currentThread(); 137 DefaultTransactionStatistics info = new DefaultTransactionStatistics(key); 138 info.split = sw.start(); 139 info.threadName = thread.getName(); 140 info.status = TransactionStatistics.Status.fromTx(tx); 141 142 info.startTimestamp = now; 143 info.startCapturedContext = new Throwable("** start invoke context **"); 144 synchronized (this) { 145 activeStatistics.put(key, info); 146 } 147 if (TransactionStatistics.Status.ACTIVE == info.status) { 148 tm.registerInterposedSynchronization(this); // register end status 149 } 150 if (log.isTraceEnabled()) { 151 log.trace(info.toString()); 152 } 153 } 154 155 @Override 156 public void threadUnassociated(Transaction tx) { 157 try { 158 Object key = ((TransactionImpl) tx).getTransactionKey(); 159 DefaultTransactionStatistics stats; 160 synchronized (DefaultTransactionMonitor.class) { 161 stats = activeStatistics.remove(key); 162 } 163 if (stats == null) { 164 log.debug(key + " not found in active statistics map"); 165 return; 166 } 167 stats.split.stop(); 168 stats.split = null; 169 if (log.isTraceEnabled()) { 170 log.trace(stats); 171 } 172 if (TransactionStatistics.Status.COMMITTED.equals(stats.status)) { 173 lastCommittedStatistics = stats; 174 } else if (TransactionStatistics.Status.ROLLEDBACK.equals(stats.status)) { 175 lastRollbackedStatistics = stats; 176 } 177 } finally { 178 MDC.remove("tx"); 179 } 180 } 181 182 @Override 183 public List<TransactionStatistics> getActiveStatistics() { 184 List<TransactionStatistics> l = new ArrayList<>(activeStatistics.values()); 185 Collections.sort(l, new Comparator<TransactionStatistics>() { 186 @Override 187 public int compare(TransactionStatistics o1, TransactionStatistics o2) { 188 return o1.getStartDate().compareTo(o2.getEndDate()); 189 } 190 }); 191 return l; 192 } 193 194 @Override 195 public long getActiveCount() { 196 return tm.getActiveCount(); 197 } 198 199 @Override 200 public long getTotalCommits() { 201 return tm.getTotalCommits(); 202 } 203 204 @Override 205 public long getTotalRollbacks() { 206 return tm.getTotalRollbacks(); 207 } 208 209 @Override 210 public TransactionStatistics getLastCommittedStatistics() { 211 return lastCommittedStatistics; 212 } 213 214 @Override 215 public TransactionStatistics getLastRollbackedStatistics() { 216 return lastRollbackedStatistics; 217 } 218 219 protected DefaultTransactionStatistics thisStatistics() { 220 Object key = tm.getTransactionKey(); 221 DefaultTransactionStatistics stats; 222 synchronized (this) { 223 stats = activeStatistics.get(key); 224 } 225 if (stats == null) { 226 log.debug(key + " not found in active statistics map"); 227 } 228 return stats; 229 } 230 231 @Override 232 public void beforeCompletion() { 233 DefaultTransactionStatistics stats = thisStatistics(); 234 if (stats == null) { 235 return; 236 } 237 stats.endCapturedContext = new Throwable("** end invoke context **"); 238 } 239 240 @Override 241 public void afterCompletion(int code) { 242 DefaultTransactionStatistics stats = thisStatistics(); 243 if (stats == null) { 244 return; 245 } 246 stats.endTimestamp = System.currentTimeMillis(); 247 stats.status = TransactionStatistics.Status.fromCode(code); 248 switch (code) { 249 case Status.STATUS_COMMITTED: 250 lastCommittedStatistics = stats; 251 break; 252 case Status.STATUS_ROLLEDBACK: 253 lastRollbackedStatistics = stats; 254 stats.endCapturedContext = new Throwable("** rollback context **"); 255 break; 256 } 257 } 258 259 @Override 260 public boolean toggle() { 261 if (enabled) { 262 tm.removeTransactionAssociationListener(this); 263 activeStatistics.clear(); 264 enabled = false; 265 } else { 266 tm.addTransactionAssociationListener(this); 267 enabled = true; 268 } 269 return enabled; 270 } 271 272 @Override 273 public boolean getEnabled() { 274 return enabled; 275 } 276 277}