package org.radargun.stages.cache.background; import java.util.*; import org.radargun.logging.Log; import org.radargun.logging.LogFactory; import org.radargun.stages.helpers.Range; import org.radargun.utils.TimeService; import org.radargun.utils.Utils; /** * Divides key space used by {@link org.radargun.stages.cache.background.Stressor}s into multiple segments, corresponding * to key range defined by {@link org.radargun.stages.cache.background.AbstractLogLogic} implementations. * Furthermore, it keeps track of currently processed key and operation performed by {@link org.radargun.stages.cache.background.LogChecker}. * * @author Radim Vansa <rvansa@redhat.com> */ public class StressorRecord { protected static final Log log = LogFactory.getLog(StressorRecord.class); protected static final boolean trace = log.isTraceEnabled(); protected final Random rand; protected Range keyRange; protected final int threadId; protected long currentKeyId; protected volatile long currentOp = -1; protected final LinkedList<StressorConfirmation> confirmations = new LinkedList<>(); private long lastUnsuccessfulCheckTimestamp = Long.MIN_VALUE; private long lastSuccessfulCheckTimestamp = TimeService.currentTimeMillis(); private Set<Long> notifiedOps = new HashSet<>(); private long requireNotify = Long.MAX_VALUE; public StressorRecord(int threadId, Range keyRange) { this.rand = new Random(threadId); log.trace("Initializing record random with " + Utils.getRandomSeed(rand)); this.threadId = threadId; this.keyRange = keyRange; privateNext(); } public StressorRecord(StressorRecord record, long operationId, long seed) { this.rand = Utils.setRandomSeed(new Random(0), seed); log.trace("Initializing record random with " + seed); this.threadId = record.threadId; this.currentOp = operationId; this.keyRange = record.keyRange; privateNext(); } public void next() { privateNext(); } // avoid calling overridable method in constructor, as object can be found in an inconsistent state private void privateNext() { currentKeyId = keyRange.getStart() + (rand.nextLong() & Long.MAX_VALUE) % keyRange.getSize(); checkFinished(currentOp++); } public String getStatus() { return String.format("thread=%d, lastStressorOperation=%d, currentOp=%d, currentKeyId=%08X, notifiedOps=%s, requireNotify=%d, " + "lastSuccessfulCheckTimestamp=%d, lastUnsuccessfulCheckTimestamp=%d.", threadId, getLastConfirmedOperationId(), currentOp, currentKeyId, notifiedOps, requireNotify, lastSuccessfulCheckTimestamp, lastUnsuccessfulCheckTimestamp); } public Object getLastConfirmedOperationId() { synchronized (confirmations) { return confirmations.isEmpty() ? -1 : confirmations.getLast().operationId; } } public int getThreadId() { return threadId; } public long getLastUnsuccessfulCheckTimestamp() { return lastUnsuccessfulCheckTimestamp; } public void setLastUnsuccessfulCheckTimestamp(long lastUnsuccessfulCheckTimestamp) { this.lastUnsuccessfulCheckTimestamp = lastUnsuccessfulCheckTimestamp; } public long getKeyId() { return currentKeyId; } public long getOperationId() { return currentOp; } public Set<Long> getNotifiedOps() { return notifiedOps; } public long getRequireNotify() { return requireNotify; } public Random getRand() { return rand; } public synchronized void notify(long operationId, Object key) { if (operationId < currentOp || !notifiedOps.add(operationId)) { log.warn("Duplicit notification for operation " + operationId + " on key " + key); } } public void checkFinished(long operationId) { // remove old confirmations synchronized (confirmations) { Iterator<StressorConfirmation> iterator = confirmations.iterator(); while (iterator.hasNext()) { StressorConfirmation confirmation = iterator.next(); if (confirmation.operationId > operationId) { break; } iterator.remove(); } } // remove old notifications synchronized (this) { notifiedOps.remove(operationId); } } public synchronized void requireNotify(long operationId) { if (operationId < requireNotify) { requireNotify = operationId; } } public synchronized boolean hasNotification(long operationId) { if (operationId < requireNotify) return true; return notifiedOps.contains(operationId); } public void setLastSuccessfulCheckTimestamp(long timestamp) { this.lastSuccessfulCheckTimestamp = timestamp; } public long getLastSuccessfulCheckTimestamp() { return lastSuccessfulCheckTimestamp; } public void addConfirmation(long operationId, long timestamp) { try { synchronized (confirmations) { ListIterator<StressorConfirmation> iterator = confirmations.listIterator(confirmations.size()); if (trace) { log.tracef("Confirmations for thread %d were %s", threadId, confirmations); } while (iterator.hasPrevious()) { StressorConfirmation confirmation = iterator.previous(); if (confirmation.operationId < operationId) { // Move index one position to the right, as it was shifted by iterator.previous() iterator.next(); confirmations.add(iterator.nextIndex(), new StressorConfirmation(operationId, timestamp)); return; } else if (confirmation.operationId == operationId) { return; } } if (confirmations.isEmpty()) { confirmations.add(new StressorConfirmation(operationId, timestamp)); } } } finally { if (trace) { log.tracef("Confirmations for thread %d are %s", threadId, confirmations); } } } /** * @return Epoch time (ms) timestamp or negative value if not confirmed yet. */ public long getCurrentConfirmationTimestamp() { synchronized (confirmations) { ListIterator<StressorConfirmation> iterator = confirmations.listIterator(confirmations.size()); while (iterator.hasPrevious()) { StressorConfirmation confirmation = iterator.previous(); if (confirmation.operationId > currentOp) { return confirmation.timestamp; } } return -1; } } public static class StressorConfirmation { public final long operationId; public final long timestamp; public StressorConfirmation(long operationId, long timestamp) { this.operationId = operationId; this.timestamp = timestamp; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; StressorConfirmation that = (StressorConfirmation) o; if (operationId != that.operationId) return false; return timestamp == that.timestamp; } @Override public int hashCode() { int result = (int) (operationId ^ (operationId >>> 32)); result = 31 * result + (int) (timestamp ^ (timestamp >>> 32)); return result; } @Override public String toString() { return "StressorConfirmation{" + "operationId=" + operationId + ", timestamp=" + timestamp + '}'; } } }