package org.infinispan.stats.wrappers; import static java.util.concurrent.TimeUnit.NANOSECONDS; import static org.infinispan.stats.container.ExtendedStatistic.LOCK_HOLD_TIME; import static org.infinispan.stats.container.ExtendedStatistic.LOCK_WAITING_TIME; import static org.infinispan.stats.container.ExtendedStatistic.NUM_HELD_LOCKS; import static org.infinispan.stats.container.ExtendedStatistic.NUM_WAITED_FOR_LOCKS; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; import org.infinispan.commons.util.CollectionFactory; import org.infinispan.context.InvocationContext; import org.infinispan.stats.CacheStatisticManager; import org.infinispan.transaction.xa.GlobalTransaction; import org.infinispan.util.TimeService; import org.infinispan.util.concurrent.locks.KeyAwareLockPromise; import org.infinispan.util.concurrent.locks.LockManager; import org.infinispan.util.concurrent.locks.LockState; import org.infinispan.util.concurrent.locks.impl.InfinispanLock; /** * Takes statistic about lock acquisition. * * @author Roberto Palmieri * @author Sebastiano Peluso * @author Diego Didona * @author Pedro Ruivo * @since 6.0 */ public class ExtendedStatisticLockManager implements LockManager { private final LockManager actual; private final CacheStatisticManager cacheStatisticManager; private final ConcurrentMap<Object, LockInfo> lockInfoMap = CollectionFactory.makeConcurrentMap(); private final TimeService timeService; public ExtendedStatisticLockManager(LockManager actual, CacheStatisticManager cacheStatisticManager, TimeService timeService) { this.cacheStatisticManager = cacheStatisticManager; this.actual = actual; this.timeService = timeService; } public final LockManager getActual() { return actual; } @Override public KeyAwareLockPromise lock(Object key, Object lockOwner, long time, TimeUnit unit) { if (lockOwnerAlreadyExists(key, lockOwner)) { return actual.lock(key, lockOwner, time, unit); } LockInfo lockInfo = new LockInfo(lockOwner instanceof GlobalTransaction ? (GlobalTransaction) lockOwner : null); updateContentionStats(key, lockInfo); final long start = timeService.time(); final KeyAwareLockPromise lockPromise = actual.lock(key, lockOwner, time, unit); lockPromise.addListener((lockedKey, state) -> { long end = timeService.time(); lockInfo.lockTimeStamp = end; if (lockInfo.contention) { lockInfo.lockWaiting = timeService.timeDuration(start, end, NANOSECONDS); } //if some owner tries to acquire the lock twice, we don't added it if (state == LockState.ACQUIRED) { lockInfoMap.putIfAbsent(lockedKey, lockInfo); } else { lockInfo.updateStats(null); //null == not locked } }); return lockPromise; } @Override public KeyAwareLockPromise lockAll(Collection<?> keys, Object lockOwner, long time, TimeUnit unit) { if (keys.size() == 1) { return lock(keys.iterator().next(), lockOwner, time, unit); } final Map<Object, LockInfo> tmpMap = new HashMap<>(); for (Object key : keys) { if (lockOwnerAlreadyExists(key, lockOwner)) { continue; } LockInfo lockInfo = new LockInfo(lockOwner instanceof GlobalTransaction ? (GlobalTransaction) lockOwner : null); updateContentionStats(key, lockInfo); tmpMap.put(key, lockInfo); } final long start = timeService.time(); final KeyAwareLockPromise lockPromise = actual.lockAll(keys, lockOwner, time, unit); lockPromise.addListener((lockedKey, state) -> { long end = timeService.time(); final LockInfo lockInfo = tmpMap.get(lockedKey); if (lockInfo == null) { return; } lockInfo.lockTimeStamp = end; if (lockInfo.contention) { lockInfo.lockWaiting = timeService.timeDuration(start, end, NANOSECONDS); } //if some owner tries to acquire the lock twice, we don't added it if (state == LockState.ACQUIRED) { lockInfoMap.putIfAbsent(lockedKey, lockInfo); } else { lockInfo.updateStats(null); //null == not locked } }); return lockPromise; } @Override public void unlock(Object key, Object lockOwner) { final long timestamp = timeService.time(); onUnlock(key, lockOwner, timestamp); actual.unlock(key, lockOwner); } @Override public void unlockAll(Collection<?> keys, Object lockOwner) { final long timestamp = timeService.time(); for (Object key : keys) { onUnlock(key, lockOwner, timestamp); } actual.unlockAll(keys, lockOwner); } @Override public void unlockAll(InvocationContext ctx) { final long timestamp = timeService.time(); final Object lockOwner = ctx.getLockOwner(); for (Object key : ctx.getLockedKeys()) { onUnlock(key, lockOwner, timestamp); } actual.unlockAll(ctx); } @Override public boolean ownsLock(Object key, Object owner) { return actual.ownsLock(key, owner); } @Override public boolean isLocked(Object key) { return actual.isLocked(key); } @Override public Object getOwner(Object key) { return actual.getOwner(key); } @Override public String printLockInfo() { return actual.printLockInfo(); } @Override public int getNumberOfLocksHeld() { return actual.getNumberOfLocksHeld(); } @Override public InfinispanLock getLock(Object key) { return actual.getLock(key); } private boolean lockOwnerAlreadyExists(Object key, Object lockOwner) { final InfinispanLock lock = actual.getLock(key); return lock != null && lock.containsLockOwner(lockOwner); } private void updateContentionStats(Object key, LockInfo lockInfo) { Object holder = getOwner(key); if (holder != null) { lockInfo.contention = !holder.equals(lockInfo.owner); } } private void onUnlock(Object key, Object lockOwner, long timestamp) { LockInfo lockInfo = lockInfoMap.get(key); if (lockInfo != null && lockInfo.owner.equals(lockOwner)) { lockInfo.updateStats(timestamp); lockInfoMap.remove(key); } } private class LockInfo { private final GlobalTransaction owner; private final boolean local; private long lockTimeStamp = -1; private boolean contention = false; private long lockWaiting = -1; public LockInfo(GlobalTransaction owner) { this.owner = owner; this.local = owner != null && !owner.isRemote(); } public final void updateStats(Long releaseTimeStamp) { boolean locked = releaseTimeStamp != null; long holdTime = !locked ? 0 : timeService.timeDuration(lockTimeStamp, releaseTimeStamp, NANOSECONDS); cacheStatisticManager.add(LOCK_HOLD_TIME, holdTime, owner, local); if (lockWaiting != -1) { cacheStatisticManager.add(LOCK_WAITING_TIME, lockWaiting, owner, local); cacheStatisticManager.increment(NUM_WAITED_FOR_LOCKS, owner, local); } if (locked) { cacheStatisticManager.increment(NUM_HELD_LOCKS, owner, local); } } } }