blob: 89c97e018f032c03bb04efd7ce627c751bf3fa20 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2014 Ericsson
*
* All rights reserved. This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0 which
* accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Alexandre Montplaisir - Renamed/extracted from CtfTraceManager
*******************************************************************************/
package org.eclipse.tracecompass.internal.tmf.ctf.core.trace.iterator;
import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.tracecompass.internal.tmf.ctf.core.Activator;
import org.eclipse.tracecompass.tmf.ctf.core.context.CtfLocationInfo;
import org.eclipse.tracecompass.tmf.ctf.core.context.CtfTmfContext;
import org.eclipse.tracecompass.tmf.ctf.core.trace.CtfTmfTrace;
/**
* A CTF trace iterator manager.
*
* Each instance of {@link CtfTmfTrace} should possess one of these, which will
* manage the iterators that are opened to read that trace. This will allow
* controlling the number of opened file handles per trace.
*
* @author Matthew Khouzam
*/
public class CtfIteratorManager {
/*
* Cache size. Under 1023 on linux32 systems. Number of file handles
* created.
*/
private static final int MAX_SIZE = 100;
/** The map of the cache */
private final Map<CtfTmfContext, CtfIterator> fMap;
/** An array pointing to the same cache. this allows fast "random" accesses */
private final List<CtfTmfContext> fRandomAccess;
/** Lock for when we access the two previous data structures */
private final Lock fAccessLock = new ReentrantLock();
/** The parent trace */
private final CtfTmfTrace fTrace;
/** Random number generator */
private final Random fRnd;
/**
* Constructor
*
* @param trace
* The trace whose iterators this manager will manage
*/
public CtfIteratorManager(CtfTmfTrace trace) {
fMap = new HashMap<>();
fRandomAccess = new ArrayList<>();
fRnd = new SecureRandom();
fTrace = trace;
}
/**
* This needs explaining: the iterator table is effectively a cache. Originally
* the contexts had a 1 to 1 structure with the file handles of a trace. This
* failed since there is a limit to how many file handles we can have opened
* simultaneously. Then a round-robin scheme was implemented, this lead up to a
* two competing contexts syncing up and using the same file handler, causing
* horrible slowdowns. Now a random replacement algorithm is selected. This is
* the same as used by arm processors, and it works quite well when many cores
* so this looks promising for very multi-threaded systems.
*
* @param context
* the context to look up
* @return the iterator referring to the context or null in the case of an error
*/
public @Nullable CtfIterator getIterator(final CtfTmfContext context) {
/*
* if the element is in the map, we don't need to do anything else.
*/
CtfIterator iter = fMap.get(context);
if (iter == null) {
fAccessLock.lock();
try {
/*
* Assign an iterator to a context.
*/
if (fRandomAccess.size() < MAX_SIZE) {
/*
* if we're not full yet, just add an element.
*/
iter = (CtfIterator) fTrace.createIterator();
if (iter == null) {
return null;
}
addElement(context, iter);
} else {
/*
* if we're full, randomly replace an element
*/
iter = replaceRandomElement(context);
}
if (context.getLocation() != null) {
final CtfLocationInfo location = (CtfLocationInfo) context.getLocation().getLocationInfo();
iter.seek(location);
}
} finally {
fAccessLock.unlock();
}
}
return iter;
}
/**
* Remove an iterator from this manager
*
* @param context
* The context of the iterator to remove
*/
public void removeIterator(CtfTmfContext context) {
fAccessLock.lock();
try {
/* The try below is only to auto-call CtfIterator.close() */
try (CtfIterator removed = fMap.remove(context)) {
// try with resource
}
fRandomAccess.remove(context);
} finally {
fAccessLock.unlock();
}
}
/**
* Add a pair of context and element to the hashmap and the arraylist.
*
* @param context
* the context
* @param elem
* the iterator
*/
private void addElement(final CtfTmfContext context,
final CtfIterator elem) {
fAccessLock.lock();
try {
fMap.put(context, elem);
fRandomAccess.add(context);
} finally {
fAccessLock.unlock();
}
}
/**
* Replace a random element
*
* @param context
* the context to swap in
* @return the iterator of the removed elements.
*/
private CtfIterator replaceRandomElement(final CtfTmfContext context) {
/*
* This needs some explanation too: We need to select a random victim
* and remove it. The order of the elements is not important, so instead
* of just calling arraylist.remove(element) which has an O(n)
* complexity, we pick an random number. The element is swapped out of
* the array and removed and replaced in the hashmap.
*/
fAccessLock.lock(); // just in case, should only be called when already locked
try {
final int size = fRandomAccess.size();
final int pos = fRnd.nextInt(size);
final CtfTmfContext victim = fRandomAccess.get(pos);
fRandomAccess.set(pos, context);
CtfIterator elem = checkNotNull(fMap.remove(victim));
if (elem.isClosed()) {
/*
* In case the iterator streams have been closed, we need to
* replace it by a fresh new one to access the trace. We also
* report that as an error as it should not happen.
*/
Activator.getDefault().logError("Found closed iterator in iterator manager for trace " + victim.getTrace()); //$NON-NLS-1$
elem.dispose();
elem = (CtfIterator) fTrace.createIterator();
}
fMap.put(context, elem);
victim.dispose();
return elem;
} finally {
fAccessLock.unlock();
}
}
/**
* Dispose this iterator manager, which will close all the remaining
* iterators.
*/
public void dispose() {
fAccessLock.lock();
try {
for (CtfIterator iterator : fMap.values()) {
iterator.dispose();
}
fMap.clear();
fRandomAccess.clear();
} finally {
fAccessLock.unlock();
}
}
}