/*
* INESC-ID, Instituto de Engenharia de Sistemas e Computadores Investigação e Desevolvimento em Lisboa
* Copyright 2013 INESC-ID and/or its affiliates and other
* contributors as indicated by the @author tags. All rights reserved.
* See the copyright.txt in the distribution for a full listing of
* individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 3.0 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.infinispan.stats;
import org.infinispan.configuration.cache.Configuration;
import org.infinispan.context.impl.TxInvocationContext;
import org.infinispan.stats.translations.ExposedStatistics.IspnStats;
import org.infinispan.transaction.xa.GlobalTransaction;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* Websiste: www.cloudtm.eu
* Date: 20/04/12
* @author Diego Didona <didona@gsd.inesc-id.pt>
* @author Pedro Ruivo
* @since 5.2
*/
public final class TransactionsStatisticsRegistry {
private static final Log log = LogFactory.getLog(TransactionsStatisticsRegistry.class);
public static final String DEFAULT_ISPN_CLASS = "DEFAULT_ISPN_CLASS";
//Now it is unbounded, we can define a MAX_NO_CLASSES
private static final Map<String, NodeScopeStatisticCollector> transactionalClassesStatsMap
= new HashMap<String, NodeScopeStatisticCollector>();
private static final ConcurrentMap<GlobalTransaction, RemoteTransactionStatistics> remoteTransactionStatistics =
new ConcurrentHashMap<GlobalTransaction, RemoteTransactionStatistics>();
private static Configuration configuration;
//Comment for reviewers: do we really need threadLocal? If I have the global id of the transaction, I can
//retrieve the transactionStatistics
private static final ThreadLocal<TransactionStatistics> thread = new ThreadLocal<TransactionStatistics>();
private static ThreadLocal<TransactionTS> lastTransactionTS = new ThreadLocal<TransactionTS>();
public static void init(Configuration configuration){
log.info("Initializing transactionalClassesMap");
TransactionsStatisticsRegistry.configuration = configuration;
transactionalClassesStatsMap.put(DEFAULT_ISPN_CLASS, new NodeScopeStatisticCollector(configuration));
}
public static void addNTBCValue(long currtime) {
TransactionStatistics txs = thread.get();
if (txs == null) {
log.debug("Trying to add NTBC " +
" but no transaction is associated to the thread");
return;
}
if(txs.getLastOpTimestamp() != 0){
txs.addValue(IspnStats.TBC_EXECUTION_TIME, currtime - txs.getLastOpTimestamp());
}
}
public static void setLastOpTimestamp(long currtime) {
TransactionStatistics txs = thread.get();
if (txs == null) {
log.debug("Trying to set Last Op Timestamp "+
" but no transaction is associated to the thread");
return;
}
txs.setLastOpTimestamp(currtime);
}
public static void addValue(IspnStats param, double value) {
TransactionStatistics txs = thread.get();
if (txs == null) {
if (log.isDebugEnabled()) {
log.debugf("Trying to add value %s to parameter %s but no transaction is associated to the thread",
value, param);
}
return;
}
txs.addValue(param, value);
}
public static void incrementValue(IspnStats param) {
TransactionStatistics txs = thread.get();
if (txs == null) {
if (log.isDebugEnabled()) {
log.debugf("Trying to increment to parameter %s but no transaction is associated to the thread", param);
}
return;
}
txs.addValue(param, 1D);
}
public static void addValueAndFlushIfNeeded(IspnStats param, double value, boolean local) {
NodeScopeStatisticCollector nssc = transactionalClassesStatsMap.get(DEFAULT_ISPN_CLASS);
if (local) {
nssc.addLocalValue(param, value);
} else {
nssc.addRemoteValue(param, value);
}
}
public static void incrementValueAndFlushIfNeeded(IspnStats param, boolean local) {
NodeScopeStatisticCollector nssc = transactionalClassesStatsMap.get(DEFAULT_ISPN_CLASS);
if (local) {
nssc.addLocalValue(param, 1D);
} else {
nssc.addRemoteValue(param, 1D);
}
}
public static void onPrepareCommand() {
//NB: If I want to give up using the InboundInvocationHandler, I can create the remote transaction
//here, just overriding the handlePrepareCommand
TransactionStatistics txs = thread.get();
if (txs == null) {
if (log.isDebugEnabled()) {
log.debug("Trying to invoke onPrepareCommand() but no transaction is associated to the thread");
}
return;
}
txs.onPrepareCommand();
}
public static void setTransactionOutcome(boolean commit) {
TransactionStatistics txs = thread.get();
if (txs == null) {
if (log.isDebugEnabled()) {
log.debugf("Trying to set outcome to %s but no transaction is associated to the thread",
commit ? "Commit" : "Rollback");
}
return;
}
txs.setCommit(commit);
}
public static void terminateTransaction() {
TransactionStatistics txs = thread.get();
if (txs == null) {
if (log.isDebugEnabled()) {
log.debug("Trying to invoke terminate() but no transaction is associated to the thread");
}
return;
}
txs.terminateTransaction();
NodeScopeStatisticCollector dest = transactionalClassesStatsMap.get(txs.getTransactionalClass());
if (dest != null) {
dest.merge(txs);
}else{
log.debug("Statistics not merged for transaction class not found on transactionalClassStatsMap");
}
thread.remove();
TransactionTS lastTS = lastTransactionTS.get();
lastTS.setEndLastTxTs(System.nanoTime());
}
public static Object getAttribute(IspnStats param,String className){
if (configuration == null) {
return null;
}
if(className == null){
return transactionalClassesStatsMap.get(DEFAULT_ISPN_CLASS).getAttribute(param);
}else{
if(transactionalClassesStatsMap.get(className) != null)
return transactionalClassesStatsMap.get(className).getAttribute(param);
else
return null;
}
}
public static Object getAttribute(IspnStats param){
if (configuration == null) {
return null;
}
return transactionalClassesStatsMap.get(DEFAULT_ISPN_CLASS).getAttribute(param);
}
public static Object getPercentile(IspnStats param, int percentile, String className){
if (configuration == null) {
return null;
}
if(className == null){
return transactionalClassesStatsMap.get(DEFAULT_ISPN_CLASS).getPercentile(param, percentile);
}else{
if(transactionalClassesStatsMap.get(className) != null)
return transactionalClassesStatsMap.get(className).getPercentile(param, percentile);
else
return null;
}
}
public static Object getPercentile(IspnStats param, int percentile){
if (configuration == null) {
return null;
}
return transactionalClassesStatsMap.get(DEFAULT_ISPN_CLASS).getPercentile(param, percentile);
}
public static void addTakenLock(Object lock) {
TransactionStatistics txs = thread.get();
if (txs == null) {
if (log.isDebugEnabled()) {
log.debugf("Trying to add lock [%s] but no transaction is associated to the thread", lock);
}
return;
}
txs.addTakenLock(lock);
}
public static void setUpdateTransaction() {
TransactionStatistics txs = thread.get();
if (txs == null) {
if (log.isDebugEnabled()) {
log.debug("Trying to invoke setUpdateTransaction() but no transaction is associated to the thread");
}
return;
}
txs.setUpdateTransaction();
}
//This is synchronized because depending on the local/remote nature, a different object is created
//Now, remote transactionStatistics get initialized at InboundInvocationHandler level
public static void initTransactionIfNecessary(TxInvocationContext tctx) {
boolean isLocal = tctx.isOriginLocal();
if(isLocal)
initLocalTransaction();
}
public static void attachRemoteTransactionStatistic(GlobalTransaction globalTransaction, boolean createIfAbsent) {
RemoteTransactionStatistics rts = remoteTransactionStatistics.get(globalTransaction);
if (rts == null && createIfAbsent && configuration != null) {
if (log.isTraceEnabled()) {
log.tracef("Create a new remote transaction statistic for transaction %s", globalTransaction.prettyPrint());
}
rts = new RemoteTransactionStatistics(configuration);
remoteTransactionStatistics.put(globalTransaction, rts);
} else if (configuration == null) {
if (log.isDebugEnabled()) {
log.debugf("Trying to create a remote transaction statistics in a not initialized Transaction Statistics Registry");
}
return;
} else {
if (log.isTraceEnabled()) {
log.tracef("Using the remote transaction statistic %s for transaction %s", rts, globalTransaction.prettyPrint());
}
}
thread.set(rts);
}
public static void detachRemoteTransactionStatistic(GlobalTransaction globalTransaction, boolean finished) {
if (thread.get() == null) {
return;
}
if (finished) {
if (log.isTraceEnabled()) {
log.tracef("Detach remote transaction statistic and finish transaction %s", globalTransaction.prettyPrint());
}
terminateTransaction();
remoteTransactionStatistics.remove(globalTransaction);
} else {
if (log.isTraceEnabled()) {
log.tracef("Detach remote transaction statistic for transaction %s", globalTransaction.prettyPrint());
}
thread.remove();
}
}
public static void reset(){
for (NodeScopeStatisticCollector nsc : transactionalClassesStatsMap.values()) {
nsc.reset();
}
}
public static boolean hasStatisticCollector() {
return thread.get() != null;
}
public static void setTransactionalClass(String className){
TransactionStatistics txs = thread.get();
if (txs == null) {
log.debug("Trying to invoke setUpdateTransaction() but no transaction is associated to the thread");
return;
}
txs.setTransactionalClass(className);
registerTransactionalClass(className);
}
private static synchronized void registerTransactionalClass(String className) {
if(transactionalClassesStatsMap.get(className) == null){
transactionalClassesStatsMap.put(className,new NodeScopeStatisticCollector(configuration));
}
}
private static void initLocalTransaction(){
//Not overriding the InitialValue method leads me to have "null" at the first invocation of get()
TransactionStatistics lts = thread.get();
if (lts == null && configuration != null) {
if (log.isTraceEnabled()) {
log.tracef("Init a new local transaction statistics");
}
thread.set(new LocalTransactionStatistics(configuration));
//Here only when transaction starts
TransactionTS lastTS = lastTransactionTS.get();
if (lastTS == null) {
log.tracef("Init a new local transaction statistics for Timestamp");
lastTransactionTS.set(new TransactionTS());
}else{
addValue(IspnStats.NTBC_EXECUTION_TIME, System.nanoTime() - lastTS.getEndLastTxTs());
incrementValue(IspnStats.NTBC_COUNT);
}
} else if (configuration == null ) {
if (log.isDebugEnabled()) {
log.debugf("Trying to create a local transaction statistics in a not initialized Transaction Statistics Registry");
}
} else {
if (log.isTraceEnabled()) {
log.tracef("Local transaction statistic is already initialized: %s", lts);
}
}
}
}