/******************************************************************************* * Copyright (c) 2014 Ericsson * * All rights reserved. This program and the accompanying materials are made * available under the terms of the Eclipse Public License v1.0 which * accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * 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.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.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 Random(System.nanoTime()); 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 */ public 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(); 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(); } } }