<commit_before>package com.cosylab.acs.maci.manager;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;


/**
 * This class is added to jmanager for work on http://jira.alma.cl/browse/COMP-6488.
 * Later it should be moved to module jacsutil.
 * 
 * @author hsommer
 * @since ACS 10.2
 */
public class ProfilingReentrantLock implements Lock
{
	private final ReentrantLock delegate;

	private final String lockName;
	
	public static final String PROFILING_ENABLED_PROPERTYNAME = "acs.enableReentrantLockProfiling";
	public static final boolean isProfilingEnabled = Boolean.getBoolean(PROFILING_ENABLED_PROPERTYNAME);
	
	public ProfilingReentrantLock(String lockName) {
		this.lockName = lockName;
		this.delegate = new ReentrantLock();
	}
	
	private static class LockInfo {
		public LockInfo(String codeId, long timeAttempted) {
			this.codeId = codeId;
			this.timeAttempted = timeAttempted;
		}
		final String codeId;
		final long timeAttempted;
		long timeLocked;
		long timeUnlocked;
	}
	
	/**
	 * key = thread id
	 */
	private Map<Long, LockInfo> lockInfoMap = new HashMap<Long, LockInfo>();
	
	@Override
	public void lock() {
		if (!lockInfoMap.containsKey(Thread.currentThread().getId())) {
			String codeId = createCodeId(1);
			long t0 = System.currentTimeMillis();
			LockInfo lockInfo = new LockInfo(codeId, t0);
			lockInfoMap.put(Thread.currentThread().getId(), lockInfo);
			delegate.lock();
			lockInfo.timeLocked = System.currentTimeMillis();
		} 
		else {
			// no profiling for nested locking
			delegate.lock();
		}
	}

	@Override
	public void lockInterruptibly() throws InterruptedException {
		throw new UnsupportedOperationException("lockInterruptibly not yet supported");
//		delegate.lockInterruptibly();
	}

	@Override
	public boolean tryLock() {
		throw new UnsupportedOperationException("tryLock not yet supported");
//		return delegate.tryLock();
	}

	@Override
	public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
		throw new UnsupportedOperationException("tryLock not yet supported");
//		return delegate.tryLock(timeout, unit);
	}

	@Override
	public void unlock() {
		delegate.unlock();
		if (!delegate.isHeldByCurrentThread()) {
			LockInfo lockInfo = lockInfoMap.remove(Thread.currentThread().getId());
			lockInfo.timeUnlocked = System.currentTimeMillis();
			printProfilingMessage(lockInfo, Thread.currentThread().getName());
		}
		else {
			// no profiling for nested unlocking
		}
	}

	@Override
	public Condition newCondition() {
		return delegate.newCondition();
	}

	
	/**
	 * @return The file name and line of file from where this method gets called, skipping an extra <code>skipLevels</code> from the call stack.
	 *         <code>null</code> if no call stack can be retrieved.
	 */
	String createCodeId(int skipLevels) {
		String ret = null;
		StackTraceElement[] stackTraceElems = (new Throwable()).getStackTrace();
		if (stackTraceElems.length >= skipLevels + 1) {
			ret = stackTraceElems[skipLevels + 1].getFileName() + "/" + stackTraceElems[skipLevels + 1].getLineNumber();
		} else {
			ret = "<unknown>";
		}
		return ret;
	}
	
	private void printProfilingMessage(LockInfo lockInfo, String threadName) {
		System.out.println("Profiling for lock '" + lockName + "', code='" + lockInfo.codeId + "', thread='" + threadName 
				+ "': acquiredMillis=" + (lockInfo.timeLocked - lockInfo.timeAttempted) + ", heldMillis=" + (lockInfo.timeUnlocked - lockInfo.timeLocked));
	}

}<commit_msg>1. Added the estimated number of competing threads to the profiling output ("queueLength") 2. Improved javadoc 3. Made detection of nested calls the same in lock and unlock methods.<commit_after>package com.cosylab.acs.maci.manager;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;


/**
 * A Lock that internally delegates to a {@link ReentrantLock} and profiles the time 
 * it takes a thread to acquire the lock, and the time for which the lock is held. 
 * When a thread calls {@link #unlock()} then these times are printed, together with the code location 
 * which used the lock.
 * <p>
 * This lock can be used as an alternative to "synchronized" blocks, when we want to detect lock contention.
 * It may also be used as an alternative to some other {@link Lock}, although at the moment only methods {@link Lock#lock()}
 * and {@link Lock#unlock()} are implemented. For the other Lock methods we throw {@link UnsupportedOperationException},
 * but it should be easy enough to also implement them when needed.
 * <p>
 * This class is added to jmanager for work on http://jira.alma.cl/browse/COMP-6488.
 * Later it should be moved to module jacsutil.
 * 
 * @author hsommer
 * @since ACS 10.2
 */
public class ProfilingReentrantLock implements Lock
{
	private final ReentrantLock delegate;

	private final String lockName;
	
	public static final String PROFILING_ENABLED_PROPERTYNAME = "acs.enableReentrantLockProfiling";
	public static final boolean isProfilingEnabled = Boolean.getBoolean(PROFILING_ENABLED_PROPERTYNAME);
	
	public ProfilingReentrantLock(String lockName) {
		this.lockName = lockName;
		this.delegate = new ReentrantLock();
	}
	
	/**
	 * Factory method that encapsulates the checking of property {@link #PROFILING_ENABLED_PROPERTYNAME}
	 * and the corresponding creation of either a ProfilingReentrantLock or a ReentrantLock.
	 * <p>
	 * We return only the base type "Lock" to keep the implementation of ProfilingReentrantLock simple for the time being. 
	 * Some methods of ReentrantLock are final, so that delegation anyway is easier than subtyping. 
	 * If we really need the additional methods, we could refactor ProfilingReentrantLock to inherit from ReentrantLock 
	 * and change the return type to ReentrantLock.
	 * <p>
	 * @TODO: Perhaps also pass a Logger and the "fair" boolean.
	 * @param lockName Only used for the profiling variant, for log output.
	 */
	public static Lock createReentrantLock(String lockName) {
		if (isProfilingEnabled) {
			return new ProfilingReentrantLock(lockName);
		}
		else {
			return new ReentrantLock();
		}
	}
	
	private static class LockInfo {
		public LockInfo(String codeId, long timeAttempted, int queueLength) {
			this.codeId = codeId;
			this.timeAttempted = timeAttempted;
			this.queueLength = queueLength;
		}
		final String codeId;
		final long timeAttempted;
		final int queueLength;
		long timeLocked;
		long timeUnlocked;
	}
	
	/**
	 * key = thread id
	 */
	private Map<Long, LockInfo> lockInfoMap = new HashMap<Long, LockInfo>();
	
	@Override
	public void lock() {
		if (!delegate.isHeldByCurrentThread()) {
			String codeId = createCodeId(1);
			long t0 = System.currentTimeMillis();
			int queueLength = delegate.getQueueLength();
			LockInfo lockInfo = new LockInfo(codeId, t0, queueLength);
			lockInfoMap.put(Thread.currentThread().getId(), lockInfo);
			delegate.lock();
			lockInfo.timeLocked = System.currentTimeMillis();
		} 
		else {
			// no profiling for nested locking, just lock
			delegate.lock();
		}
	}

	@Override
	public void lockInterruptibly() throws InterruptedException {
		throw new UnsupportedOperationException("lockInterruptibly not yet supported");
//		delegate.lockInterruptibly();
	}

	@Override
	public boolean tryLock() {
		throw new UnsupportedOperationException("tryLock not yet supported");
//		return delegate.tryLock();
	}

	@Override
	public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
		throw new UnsupportedOperationException("tryLock not yet supported");
//		return delegate.tryLock(timeout, unit);
	}

	@Override
	public void unlock() {
		delegate.unlock();
		if (!delegate.isHeldByCurrentThread()) {
			LockInfo lockInfo = lockInfoMap.remove(Thread.currentThread().getId());
			lockInfo.timeUnlocked = System.currentTimeMillis();
			printProfilingMessage(lockInfo, Thread.currentThread().getName());
		}
		else {
			// no profiling for nested unlocking when we still hold the lock
		}
	}

	@Override
	public Condition newCondition() {
		return delegate.newCondition();
	}

	
	/**
	 * @return The file name and line of file from where this method gets called, skipping an extra <code>skipLevels</code> from the call stack.
	 *         <code>null</code> if no call stack can be retrieved.
	 */
	String createCodeId(int skipLevels) {
		String ret = null;
		StackTraceElement[] stackTraceElems = (new Throwable()).getStackTrace();
		if (stackTraceElems.length >= skipLevels + 1) {
			ret = stackTraceElems[skipLevels + 1].getFileName() + "/" + stackTraceElems[skipLevels + 1].getLineNumber();
		} else {
			ret = "<unknown>";
		}
		return ret;
	}
	
	private void printProfilingMessage(LockInfo lockInfo, String threadName) {
		System.out.println("Profiling for lock '" + lockName + "' used in code='" + lockInfo.codeId + "', thread='" + threadName 
				+ "': queueLength=" + lockInfo.queueLength + ", acquiredMillis=" + (lockInfo.timeLocked - lockInfo.timeAttempted) + ", heldMillis=" + (lockInfo.timeUnlocked - lockInfo.timeLocked));
	}

}
{'fraction_non_alphanumeric': 0.10261374636979671, 'fraction_numerical': 0.0031192857911154135, 'mean_word_length': 4.3964016250725475, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '263733', 'n_tokens_mistral': 2977, 'n_tokens_neox': 2713, 'n_words': 830}