blob: 7892cde6dedd838e7f9143a7e81f90174daa9270 [file] [log] [blame]
/*
* Copyright (c) 2014-2016 Eike Stepper (Berlin, Germany) and others.
* 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:
* Eike Stepper - initial API and implementation
*/
package org.eclipse.oomph.p2.internal.core;
import org.eclipse.oomph.util.CollectionUtil;
import org.eclipse.oomph.util.IOUtil;
import org.eclipse.oomph.util.OfflineMode;
import org.eclipse.oomph.util.PropertiesUtil;
import org.eclipse.oomph.util.ReflectUtil;
import org.eclipse.oomph.util.ReflectUtil.ReflectionException;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.equinox.internal.p2.artifact.repository.Activator;
import org.eclipse.equinox.internal.p2.artifact.repository.ArtifactRepositoryManager;
import org.eclipse.equinox.internal.p2.artifact.repository.MirrorSelector;
import org.eclipse.equinox.internal.p2.artifact.repository.simple.SimpleArtifactRepository;
import org.eclipse.equinox.internal.p2.core.helpers.LogHelper;
import org.eclipse.equinox.internal.p2.core.helpers.OrderedProperties;
import org.eclipse.equinox.internal.p2.engine.CommitOperationEvent;
import org.eclipse.equinox.internal.p2.engine.RollbackOperationEvent;
import org.eclipse.equinox.internal.p2.metadata.repository.MetadataRepositoryManager;
import org.eclipse.equinox.internal.p2.repository.DownloadStatus;
import org.eclipse.equinox.internal.p2.repository.Transport;
import org.eclipse.equinox.internal.p2.repository.helpers.AbstractRepositoryManager;
import org.eclipse.equinox.internal.p2.repository.helpers.LocationProperties;
import org.eclipse.equinox.internal.provisional.p2.core.eventbus.IProvisioningEventBus;
import org.eclipse.equinox.internal.provisional.p2.core.eventbus.SynchronousProvisioningListener;
import org.eclipse.equinox.internal.provisional.p2.repository.RepositoryEvent;
import org.eclipse.equinox.p2.core.IAgentLocation;
import org.eclipse.equinox.p2.core.IProvisioningAgent;
import org.eclipse.equinox.p2.core.ProvisionException;
import org.eclipse.equinox.p2.metadata.IArtifactKey;
import org.eclipse.equinox.p2.metadata.IInstallableUnit;
import org.eclipse.equinox.p2.repository.IRepository;
import org.eclipse.osgi.util.NLS;
import java.io.File;
import java.io.FileNotFoundException;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EventObject;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
/**
* @author Eike Stepper
*/
@SuppressWarnings("restriction")
public class CachingRepositoryManager<T>
{
public static final String BOGUS_SCHEME = "bogus";
private static final Method METHOD_checkValidLocation = ReflectUtil.getMethod(AbstractRepositoryManager.class, "checkValidLocation", URI.class);
private static final Method METHOD_enterLoad = ReflectUtil.getMethod(AbstractRepositoryManager.class, "enterLoad", URI.class, IProgressMonitor.class);
private static final Method METHOD_basicGetRepository = ReflectUtil.getMethod(AbstractRepositoryManager.class, "basicGetRepository", URI.class);
private static final Method METHOD_fail = ReflectUtil.getMethod(AbstractRepositoryManager.class, "fail", URI.class, int.class);
private static final Method METHOD_addRepository1 = ReflectUtil.getMethod(AbstractRepositoryManager.class, "addRepository", URI.class, boolean.class,
boolean.class);
private static final Method METHOD_loadIndexFile = ReflectUtil.getMethod(AbstractRepositoryManager.class, "loadIndexFile", URI.class, IProgressMonitor.class);
private static final Method METHOD_getPreferredRepositorySearchOrder = ReflectUtil.getMethod(AbstractRepositoryManager.class,
"getPreferredRepositorySearchOrder", LocationProperties.class);
private static final Method METHOD_getAllSuffixes = ReflectUtil.getMethod(AbstractRepositoryManager.class, "getAllSuffixes");
private static final Method METHOD_loadRepository = ReflectUtil.getMethod(AbstractRepositoryManager.class, "loadRepository", URI.class, String.class,
String.class, int.class, SubMonitor.class);
private static final Method METHOD_addRepository2 = ReflectUtil.getMethod(AbstractRepositoryManager.class, "addRepository", IRepository.class, boolean.class,
String.class);
private static final Method METHOD_removeRepository = ReflectUtil.getMethod(AbstractRepositoryManager.class, "removeRepository", URI.class, boolean.class);
private static final Method METHOD_exitLoad = ReflectUtil.getMethod(AbstractRepositoryManager.class, "exitLoad", URI.class);
private static final Method METHOD_broadcastChangeEvent = ReflectUtil.getMethod(AbstractRepositoryManager.class, "broadcastChangeEvent", URI.class, int.class,
int.class, boolean.class);
private static final String PROPERTY_VERSION = "version";
private static final String PROP_BETTER_MIRROR_SELECTION = "oomph.p2.mirror";
private static boolean betterMirrorSelection;
private final AbstractRepositoryManager<T> delegate;
private final int repositoryType;
private final CachingTransport transport;
public CachingRepositoryManager(AbstractRepositoryManager<T> delegate, int repositoryType, CachingTransport transport)
{
this.delegate = delegate;
IAgentLocation agentLocation = ReflectUtil.getValue("agentLocation", delegate);
if (agentLocation != null)
{
URI rootLocation = agentLocation.getRootLocation();
if (rootLocation != null)
{
if (!IOUtil.canWriteFolder(new File(rootLocation.getPath())))
{
ReflectUtil.setValue("agentLocation", delegate, null);
}
}
}
this.repositoryType = repositoryType;
if (transport == null)
{
Object t = delegate.getAgent().getService(Transport.SERVICE_NAME);
if (t instanceof CachingTransport)
{
transport = (CachingTransport)t;
}
}
this.transport = transport;
}
public CachingTransport getTransport()
{
return transport;
}
public IRepository<T> loadRepository(URI location, IProgressMonitor monitor, String type, int flags) throws ProvisionException
{
checkValidLocation(location);
SubMonitor sub = SubMonitor.convert(monitor, 100);
boolean added = false;
IRepository<T> result = null;
CachingTransport.startLoadingRepository(location);
try
{
enterLoad(location, sub.newChild(5));
try
{
result = basicGetRepository(location);
if (result != null)
{
return result;
}
// Add the repository first so that it will be enabled, but don't send add event until after the load.
added = addRepository(location, true, false);
LocationProperties indexFile = loadIndexFile(location, sub.newChild(15));
String[] preferredOrder = getPreferredRepositorySearchOrder(indexFile);
String[] allSuffixes = getAllSuffixes();
String[] suffixes = sortSuffixes(allSuffixes, preferredOrder);
sub = SubMonitor.convert(sub, NLS.bind("Adding repository {0}", location), suffixes.length * 100);
ProvisionException failure = null;
try
{
for (int i = 0; i < suffixes.length; i++)
{
if (sub.isCanceled())
{
throw new OperationCanceledException();
}
try
{
result = loadRepository(location, suffixes[i], type, flags, sub.newChild(100));
}
catch (ProvisionException e)
{
if (!(e.getStatus().getException() instanceof FileNotFoundException))
{
failure = e;
break;
}
}
if (result != null)
{
addRepository(result, false, suffixes[i]);
cacheIndexFile(location, suffixes[i]);
break;
}
}
}
finally
{
sub.done();
}
if (result == null)
{
// If we just added the repository, remove it because it cannot be loaded.
if (added)
{
removeRepository(location, false);
}
// Eagerly cleanup missing system repositories.
if (Boolean.parseBoolean(delegate.getRepositoryProperty(location, IRepository.PROP_SYSTEM)))
{
delegate.removeRepository(location);
}
if (failure != null)
{
throw failure;
}
fail(location, ProvisionException.REPOSITORY_NOT_FOUND);
}
}
finally
{
exitLoad(location);
}
}
finally
{
CachingTransport.stopLoadingRepository();
}
// Broadcast the add event after releasing lock.
if (added)
{
broadcastChangeEvent(location, repositoryType, RepositoryEvent.ADDED, true);
}
return result;
}
private File getCachedIndexFile(URI location)
{
try
{
String path = location.toString();
if (!path.endsWith("/"))
{
path += "/";
}
return transport.getCacheFile(new URI(path + "p2.index"));
}
catch (URISyntaxException ex)
{
// Can't happen.
throw new RuntimeException(ex);
}
}
private void cacheIndexFile(URI location, String suffix)
{
if ("file".equals(location.getScheme()))
{
return;
}
File cachedIndexFile = getCachedIndexFile(location);
Map<String, String> properties = PropertiesUtil.getProperties(cachedIndexFile);
if (!properties.containsKey(PROPERTY_VERSION))
{
properties.put(PROPERTY_VERSION, "1");
}
if (repositoryType == IRepository.TYPE_METADATA)
{
properties.put("metadata.repository.factory.order", suffix);
}
else
{
properties.put("artifact.repository.factory.order", suffix);
}
// Cleanup; can be removed at some point in the future...
properties.remove("generated");
PropertiesUtil.saveProperties(cachedIndexFile, properties, false);
}
private URI checkValidLocation(URI location)
{
return (URI)ReflectUtil.invokeMethod(METHOD_checkValidLocation, delegate, location);
}
private void enterLoad(URI location, IProgressMonitor monitor)
{
ReflectUtil.invokeMethod(METHOD_enterLoad, delegate, location, monitor);
}
@SuppressWarnings("unchecked")
protected IRepository<T> basicGetRepository(URI location)
{
return (IRepository<T>)ReflectUtil.invokeMethod(METHOD_basicGetRepository, delegate, location);
}
// private boolean checkNotFound(URI location)
// {
// return (Boolean)ReflectUtil.invokeMethod(METHOD_checkNotFound, delegate, location);
// }
//
// private void rememberNotFound(URI location)
// {
// ReflectUtil.invokeMethod(METHOD_rememberNotFound, delegate, location);
// }
private void fail(URI location, int code) throws ProvisionException
{
try
{
ReflectUtil.invokeMethod(METHOD_fail, delegate, location, code);
}
catch (ReflectionException ex)
{
Throwable cause = ex.getCause();
if (cause instanceof ProvisionException)
{
throw (ProvisionException)cause;
}
throw ex;
}
}
private boolean addRepository(URI location, boolean isEnabled, boolean signalAdd)
{
return (Boolean)ReflectUtil.invokeMethod(METHOD_addRepository1, delegate, location, isEnabled, signalAdd);
}
private LocationProperties loadIndexFile(URI location, IProgressMonitor monitor)
{
return (LocationProperties)ReflectUtil.invokeMethod(METHOD_loadIndexFile, delegate, location, monitor);
}
protected String[] getPreferredRepositorySearchOrder(LocationProperties properties)
{
return (String[])ReflectUtil.invokeMethod(METHOD_getPreferredRepositorySearchOrder, delegate, properties);
}
protected String[] getAllSuffixes()
{
return (String[])ReflectUtil.invokeMethod(METHOD_getAllSuffixes, delegate);
}
private String[] sortSuffixes(String[] allSuffixes, String[] preferredOrder)
{
List<String> suffixes = new ArrayList<String>(Arrays.asList(allSuffixes));
for (int i = preferredOrder.length - 1; i >= 0; --i)
{
String suffix = preferredOrder[i].trim();
if (!LocationProperties.END.equals(suffix))
{
suffixes.remove(suffix);
suffixes.add(0, suffix);
}
}
return suffixes.toArray(new String[suffixes.size()]);
}
@SuppressWarnings("unchecked")
private IRepository<T> loadRepository(URI location, String suffix, String type, int flags, SubMonitor monitor) throws ProvisionException
{
try
{
return (IRepository<T>)ReflectUtil.invokeMethod(METHOD_loadRepository, delegate, location, suffix, type, flags, monitor);
}
catch (ReflectionException ex)
{
Throwable cause = ex.getCause();
if (cause instanceof ProvisionException)
{
throw (ProvisionException)cause;
}
throw ex;
}
}
protected void addRepository(IRepository<T> repository, boolean signalAdd, String suffix)
{
ReflectUtil.invokeMethod(METHOD_addRepository2, delegate, repository, signalAdd, suffix);
}
private boolean removeRepository(URI toRemove, boolean signalRemove)
{
return (Boolean)ReflectUtil.invokeMethod(METHOD_removeRepository, delegate, toRemove, signalRemove);
}
private void exitLoad(URI location)
{
ReflectUtil.invokeMethod(METHOD_exitLoad, delegate, location);
}
private void broadcastChangeEvent(URI location, int repositoryType, int kind, boolean isEnabled)
{
ReflectUtil.invokeMethod(METHOD_broadcastChangeEvent, delegate, location, repositoryType, kind, isEnabled);
}
public static boolean isBetterMirrorSelection()
{
return betterMirrorSelection;
}
public static boolean enableBetterMirrorSelection()
{
boolean originalBetterMirrorSelection = betterMirrorSelection;
setBetterMirrorSelection(!"false".equals(PropertiesUtil.getProperty(PROP_BETTER_MIRROR_SELECTION)));
return originalBetterMirrorSelection;
}
public static void setBetterMirrorSelection(boolean betterMirrorSelection)
{
CachingRepositoryManager.betterMirrorSelection = betterMirrorSelection;
}
/**
* @author Eike Stepper
*/
public static class Metadata extends MetadataRepositoryManager
{
private final CachingRepositoryManager<IInstallableUnit> loader;
public Metadata(IProvisioningAgent agent, CachingTransport transport)
{
super(agent);
loader = new CachingRepositoryManager<IInstallableUnit>(this, IRepository.TYPE_METADATA, transport);
}
@Override
protected IRepository<IInstallableUnit> loadRepository(URI location, IProgressMonitor monitor, String type, int flags) throws ProvisionException
{
return loader.loadRepository(location, monitor, type, flags);
}
@Override
public URI[] getKnownRepositories(int flags)
{
return filter(super.getKnownRepositories(flags));
}
/**
* Work-around for bug 483286.
*/
@Override
public void flushCache()
{
synchronized (repositoryLock)
{
if (repositories != null)
{
super.flushCache();
}
}
}
static URI[] filter(URI[] uris)
{
List<URI> result = new ArrayList<URI>(uris.length);
for (URI uri : uris)
{
try
{
if (!"file".equalsIgnoreCase(uri.getScheme()) || new File(uri).isDirectory())
{
result.add(uri);
}
}
catch (IllegalArgumentException ex)
{
//$FALL-THROUGH$
}
}
return result.toArray(new URI[result.size()]);
}
}
/**
* @author Eike Stepper
*/
public static class Artifact extends ArtifactRepositoryManager
{
private static final String GLOBAL_MAX_THREADS = Activator.getContext().getProperty(SimpleArtifactRepository.PROP_MAX_THREADS);
private final CachingRepositoryManager<IArtifactKey> loader;
public Artifact(IProvisioningAgent agent, CachingTransport transport)
{
super(agent);
loader = new CachingRepositoryManager<IArtifactKey>(this, IRepository.TYPE_ARTIFACT, transport);
}
@Override
protected IRepository<IArtifactKey> loadRepository(URI location, IProgressMonitor monitor, String type, int flags) throws ProvisionException
{
// Inspect the repository if better mirror selection is enabled,
// the repository is simple
// and the repository not modifiable, i.e., if it's not a local file-based repository.
IRepository<IArtifactKey> result = loader.loadRepository(location, monitor, type, flags);
if (isBetterMirrorSelection() && result instanceof SimpleArtifactRepository && !result.isModifiable())
{
// There should always be an event bus.
IProvisioningEventBus eventBus = (IProvisioningEventBus)getAgent().getService(IProvisioningEventBus.SERVICE_NAME);
if (eventBus != null)
{
// Create our improved mirror selector and reflectively set it to be the one used by this repository.
final BetterMirrorSelector mirrorSelector = new BetterMirrorSelector(result, loader.getTransport(), eventBus);
ReflectUtil.setValue("mirrors", result, mirrorSelector);
// Because of the poor implementation in org.eclipse.equinox.internal.p2.artifact.repository.simple.SimpleArtifactRepository.getMaximumThreads()
// which was reported in https://bugs.eclipse.org/bugs/show_bug.cgi?id=471861 but is not yet fixed,
// we work the around the problem by setting the global PROP_MAX_THREADS into the repository properties.
if (GLOBAL_MAX_THREADS != null)
{
Map<String, String> properties = ReflectUtil.getValue("properties", result);
Map<String, String> specializedProperties = new OrderedProperties()
{
@Override
public Set<java.util.Map.Entry<String, String>> entrySet()
{
if (!OfflineMode.isEnabled())
{
// If the request for the entry set occurs when p2 is determining the maximum number of threads allowed by the repository...
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
if (stackTrace.length > 5 && "getMaximumThreads".equals(stackTrace[5].getMethodName()))
{
// If the repository itself doesn't restrict the maximum number of threads...
String respositoryMaxThreads = get(SimpleArtifactRepository.PROP_MAX_THREADS);
if (respositoryMaxThreads == null)
{
// Initialize our specialized mirror selector.
mirrorSelector.initMirrorActivities(new NullProgressMonitor());
// If there is no list of mirrors, allow 10 threads, otherwise, allow 10 threads per mirror or the global maximum, whichever is less.
// Save that value as if it were a property in the repository.
BetterMirrorSelector.MirrorActivity[] mirrorActivities = mirrorSelector.mirrorActivities;
int maxThreads = mirrorActivities.length <= 1 ? 10 : Math.min(mirrorActivities.length * 10, Integer.parseInt(GLOBAL_MAX_THREADS));
put(SimpleArtifactRepository.PROP_MAX_THREADS, Integer.toString(maxThreads));
}
}
}
return super.entrySet();
}
};
// Copy over the properties and insert our specialized map reflective into the field of the base class.
specializedProperties.putAll(properties);
ReflectUtil.setValue("properties", result, specializedProperties);
}
}
}
return result;
}
@Override
public URI[] getKnownRepositories(int flags)
{
return Metadata.filter(super.getKnownRepositories(flags));
}
/**
* Work-around for bug 483286.
*/
@Override
public void flushCache()
{
synchronized (repositoryLock)
{
if (repositories != null)
{
super.flushCache();
}
}
}
/**
* @author Eike Stepper
* @author Ed Merks
*/
static final class BetterMirrorSelector extends MirrorSelector implements SynchronousProvisioningListener
{
/**
* The main artifact repository.
*/
private final URI repositoryURI;
/**
* The event bus to which this selector listens.
*/
private final IProvisioningEventBus eventBus;
/**
* A map from mirror URI to the artifact activity for that mirror.
*/
private final Map<URI, ArtifactActivity> artifactActivities = Collections.synchronizedMap(new HashMap<URI, ArtifactActivity>());
/**
* The phase in which we are using mirrors.
* During the initial phase, each mirror is probed to measure performance.
* Subsequently only the faster mirrors are used.
*/
private int phase = 1;
/**
* The per-mirror activities being managed.
*/
private MirrorActivity[] mirrorActivities;
public BetterMirrorSelector(IRepository<?> repository, Transport transport, IProvisioningEventBus eventBus)
{
super(repository, transport);
this.eventBus = eventBus;
repositoryURI = getBaseURI();
}
public void notify(EventObject event)
{
// If we have mirror activities.
if (mirrorActivities != null)
{
// If it's a download event.
if (event instanceof DownloadArtifactEvent)
{
// Get the corresponding artifact activity for this event, if there is one..
DownloadArtifactEvent downloadArtifactEvent = (DownloadArtifactEvent)event;
URI artifactURI = downloadArtifactEvent.getArtifactURI();
ArtifactActivity artifactActivity = artifactActivities.get(artifactURI);
if (artifactActivity != null)
{
// Get the corresponding overall mirror activity.
MirrorActivity mirrorActivity = artifactActivity.getMirrorActivity();
if (downloadArtifactEvent.isCompleted())
{
// If the download is completed, reduce the usage count of this mirror.
mirrorActivity.decrementUsage();
// Determine if it was successful or not.
IStatus status = downloadArtifactEvent.getStatus();
if (status.isOK())
{
// Increment the success count of this mirror.
mirrorActivity.incrementSuccess();
// Record the overall end-to-end speed based or course on the overall time for the download the start and complete along with the number of
// bytes.
DownloadStatus downloadStatus = (DownloadStatus)status;
long fileSize = downloadStatus.getFileSize();
artifactActivity.setSize(fileSize);
mirrorActivity.setSpeed(artifactActivity.getSpeed());
}
else
{
// There was a failure.
mirrorActivity.incrementFailure();
// So no bytes were successfully downloaded.
artifactActivity.setSize(0);
// This will reduce the average speed of the mirror in half, quickly dropping it from consideration for use.
mirrorActivity.setSpeed(0);
}
}
else
{
// If the download is started, increment the usage count of the mirror.
mirrorActivity.incrementUsage();
// Record the point in time at which this activity started.
artifactActivity.setStart();
}
}
}
else if (event instanceof CommitOperationEvent || event instanceof RollbackOperationEvent)
{
// If we complete or we roll back, we remove this as a listener.
if (eventBus != null)
{
eventBus.removeListener(this);
}
// And reset our state to the intial state for subsequent reuse.
mirrorActivities = null;
artifactActivities.clear();
phase = 1;
}
}
}
@Override
public synchronized URI getMirrorLocation(URI inputLocation, IProgressMonitor monitor)
{
// If we don't know the repository URI, we can't request mirrors.
Assert.isNotNull(inputLocation);
if (repositoryURI == null)
{
return inputLocation;
}
// Determine the relative location of the download with respect to the repository.
URI relativeLocation = repositoryURI.relativize(inputLocation);
if (relativeLocation == null || relativeLocation.isAbsolute())
{
// If it's not relative, we just use the given URI.
return inputLocation;
}
// Initialize the mirrors with a mirror request to populate the list.
initMirrorActivities(monitor);
// Select the mirror to be used for the artifact activity.
MirrorActivity selectedMirror = selectMirror();
if (selectedMirror == null)
{
// If there isn't one, we must use the given input.
return inputLocation;
}
String location = selectedMirror.getLocation();
try
{
// Determine the location within the mirror, create a new artifact activity for it, and see if there was previous such an activity.
URI artifactURI = new URI(location + relativeLocation.getPath());
ArtifactActivity newActivity = new ArtifactActivity(selectedMirror);
ArtifactActivity previousActivity = artifactActivities.put(artifactURI, newActivity);
if (previousActivity != null)
{
if (!newActivity.retry(previousActivity))
{
// Return a bogus URI that will refuse to try this artifactURI from this mirror again.
return new URI(BOGUS_SCHEME + ":" + artifactURI);
}
// We don't expect to have repeated requests for the same artifact.
// But this does happen if it's a .pack.gz produced by a version of Java newer than the version of Java being used by this process.
monitor.subTask("Repeated attempts to download " + artifactURI + " probably because it can't be processed");
}
// Use this location in the mirror instead of the original URI.
return artifactURI;
}
catch (URISyntaxException e)
{
log("Unable to make location " + inputLocation + " relative to mirror " + location, e);
}
// If all else fails, we must use the original URI.
return inputLocation;
}
private MirrorActivity selectMirror()
{
// If we don't have mirrors, we can't select one.
if (mirrorActivities.length == 0)
{
return null;
}
if (phase == 1)
{
// During phase one, we try to probe each mirror once to measure speed.
for (MirrorActivity mirrorActivity : mirrorActivities)
{
if (!mirrorActivity.isProbed())
{
mirrorActivity.setProbed();
return mirrorActivity;
}
}
// Once all mirrors are probed, we enter phase two.
phase = 2;
}
// We sort the mirrors based on performance, favoring faster mirrors of course.
sort();
for (MirrorActivity mirrorActivity : mirrorActivities)
{
// We try to limit the number of simultaneous activities per mirror to avoid overloading any one mirror.
int MAX_ACTIVITY_PER_HOST = 4;
int usages = mirrorActivity.getUsages();
if (usages < MAX_ACTIVITY_PER_HOST)
{
// Return the fasted one that's not so busy.
return mirrorActivity;
}
}
// Find the least used mirror in the first half of the available mirrors.
MirrorActivity leastUsedMirror = null;
for (MirrorActivity mirrorActivity : mirrorActivities)
{
int usages = mirrorActivity.getUsages();
if (leastUsedMirror == null || leastUsedMirror.getUsages() > usages)
{
leastUsedMirror = mirrorActivity;
}
}
return leastUsedMirror;
}
public List<String> getStats()
{
// Compute statistics for the mirror speeds.
List<String> result = new ArrayList<String>();
if (mirrorActivities != null)
{
// We'll create new mirror activities to summarize the result.
MirrorActivity[] summaryMirrorActivities = new MirrorActivity[mirrorActivities.length];
for (int i = 0; i < mirrorActivities.length; ++i)
{
MirrorActivity mirrorActivity = mirrorActivities[i];
MirrorActivity summaryMirrorActivity = summaryMirrorActivities[i] = new MirrorActivity(mirrorActivity.location);
for (int j = 0; j < mirrorActivity.getFailures(); ++j)
{
// Record the number of failures.
summaryMirrorActivity.incrementFailure();
}
// Compute the total number of bytes and the total time taken to download them for all the artifact activities.
long totalSize = 0;
long totalDuration = 0;
for (ArtifactActivity artifactActivity : artifactActivities.values())
{
if (artifactActivity.getMirrorActivity() == mirrorActivity)
{
summaryMirrorActivity.incrementSuccess();
totalSize += artifactActivity.getSize();
totalDuration += artifactActivity.getDuration();
}
}
if (totalSize != 0)
{
// Record this as the overall result of the mirror activity.
summaryMirrorActivity.setSpeed(1000 * totalSize / totalDuration);
}
}
// Replace with the summary.
mirrorActivities = summaryMirrorActivities;
// Sort them again.
sort();
// Add only the ones that actually were used to the final result.
for (MirrorActivity mirrorActivity : mirrorActivities)
{
if (mirrorActivity.getSuccesses() + mirrorActivity.getFailures() > 0)
{
result.add(getStats(mirrorActivity));
}
}
}
return result;
}
private String getStats(MirrorActivity mirrorActivity)
{
// Compute a nice string representation for the mirror activity.
NumberFormat numberFormat = NumberFormat.getInstance(Locale.US);
int successes = mirrorActivity.getSuccesses();
int failures = mirrorActivity.getFailures();
String message = "Mirrored " + successes + " artifacts from " + mirrorActivity.getLocation() + " at "
+ numberFormat.format(mirrorActivity.getSpeed() / 1000) + "kb/s";
if (failures > 0)
{
message += " with " + failures + " failures";
}
return message;
}
private void sort()
{
// Compute a sorted map from the speed to the mirror activity with that speed.
Map<Long, Set<MirrorActivity>> sortedMap = new TreeMap<Long, Set<MirrorActivity>>();
for (MirrorActivity mirrorActivity : mirrorActivities)
{
long value = mirrorActivity.getSpeed();
CollectionUtil.add(sortedMap, value, mirrorActivity);
}
// Flatten out the values of the map back into the array.
int index = mirrorActivities.length;
for (Set<MirrorActivity> acitivities : sortedMap.values())
{
for (MirrorActivity mirrorActivity : acitivities)
{
mirrorActivities[--index] = mirrorActivity;
}
}
}
private void initMirrorActivities(IProgressMonitor monitor)
{
// Do this only once.
if (mirrorActivities == null)
{
// Delete to p2's implementation.
Method method = ReflectUtil.getMethod(this, "initMirrors", IProgressMonitor.class);
MirrorInfo[] mirrors = (MirrorInfo[])ReflectUtil.invokeMethod(method, this, monitor);
if (mirrors == null)
{
mirrors = new MirrorInfo[0];
}
// Use only the first two thirds of the list.
int limit = (mirrors.length + 2) / 3;
if (limit == 0 && repositoryURI != null)
{
// Even if there are no mirrors treat it as a single mirror so that activities and performance can always be monitored.
String locationString = repositoryURI.toString();
if (!locationString.endsWith("/"))
{
locationString += Character.toString('/');
}
mirrorActivities = new MirrorActivity[] { new MirrorActivity(locationString) };
}
else
{
mirrorActivities = new MirrorActivity[limit];
for (int i = 0; i < limit; i++)
{
// Convert the mirror info to our mirror activity representation.
MirrorInfo mirrorInfo = mirrors[i];
String locationString = getLocationString(mirrorInfo);
MirrorActivity mirrorActivity = new MirrorActivity(locationString);
mirrorActivities[i] = mirrorActivity;
}
}
// Do ourselves as a listener.
if (eventBus != null)
{
eventBus.addListener(this);
}
}
}
private URI getBaseURI()
{
return ReflectUtil.getValue("baseURI", this);
}
private static String getLocationString(MirrorInfo mirrorInfo)
{
return ReflectUtil.getValue("locationString", mirrorInfo);
}
private static void log(String message, Throwable exception)
{
LogHelper.log(new Status(IStatus.ERROR, Activator.ID, message, exception));
}
/**
* @author Ed Merks
*/
private static class ArtifactActivity
{
private final MirrorActivity mirrorActivity;
private long start;
private long end;
private long size;
private int retryCount;
public ArtifactActivity(MirrorActivity mirrorActivity)
{
this.mirrorActivity = mirrorActivity;
}
public MirrorActivity getMirrorActivity()
{
return mirrorActivity;
}
public void setStart()
{
this.start = System.currentTimeMillis();
}
public void setSize(long size)
{
end = System.currentTimeMillis();
this.size = size;
}
public long getDuration()
{
return end - start;
}
public long getSpeed()
{
return size * 1000 / (end - start);
}
public long getSize()
{
return size;
}
public boolean retry(ArtifactActivity previousArtifactActivity)
{
if (previousArtifactActivity != null)
{
retryCount = previousArtifactActivity.retryCount + 1;
}
return retryCount < 3;
}
@Override
public String toString()
{
return "ArtifactActivity [start=" + start + ", end=" + end + ", size=" + size + ", duration=" + getDuration() + ", speed=" + getSpeed() + "]";
}
}
/**
* @author Ed Merks
*/
private static class MirrorActivity
{
private final String location;
private final AtomicInteger usages = new AtomicInteger();
private final AtomicInteger successes = new AtomicInteger();
private final AtomicInteger failures = new AtomicInteger();
private final AtomicLong speed = new AtomicLong();
private boolean probed;
public MirrorActivity(String location)
{
this.location = location;
}
public String getLocation()
{
return location;
}
public boolean isProbed()
{
return probed;
}
public void setProbed()
{
probed = true;
}
public int getUsages()
{
return usages.get();
}
public void incrementUsage()
{
usages.incrementAndGet();
}
public void decrementUsage()
{
usages.decrementAndGet();
}
public int getSuccesses()
{
return successes.get();
}
public void incrementSuccess()
{
successes.incrementAndGet();
}
public int getFailures()
{
return failures.get();
}
public void incrementFailure()
{
failures.incrementAndGet();
}
public synchronized long getSpeed()
{
return speed.get();
}
public synchronized void setSpeed(long speed)
{
long currentSpeed = this.speed.get();
if (currentSpeed == 0)
{
currentSpeed = speed;
}
else
{
currentSpeed = (currentSpeed + speed) / 2;
}
this.speed.set(currentSpeed);
}
@Override
public String toString()
{
return "MirrorActivity [location=" + location + ", usages=" + usages.get() + ", successes=" + successes.get() + ", failures=" + failures.get()
+ ", speed=" + speed.get() + ", probed=" + probed + "]";
}
}
}
}
}