/*
* Copyright 2013 Thomas Bocek
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package net.tomp2p.peers;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.tomp2p.p2p.MaintenanceTask;
import net.tomp2p.utils.ConcurrentCacheMap;
import net.tomp2p.utils.Timings;
/**
* The default maintenance implementation.
*
* @author Thomas Bocek
*
*/
public class DefaultMaintenance implements Maintenance {
private static final Logger LOG = LoggerFactory.getLogger(DefaultMaintenance.class);
private final int peerUrgency;
private final int[] intervalSeconds;
private final List<Map<Number160, PeerStatatistic>> peerMapVerified;
private final List<Map<Number160, PeerStatatistic>> peerMapNonVerified;
private final ConcurrentCacheMap<Number160, PeerAddress> offlineMap;
/**
* Creates a new maintenance class with the verified and non verified map.
*
* @param peerMapVerified
* The verified map
* @param peerMapNonVerified
* The non-verified map
* @param offlineMap
* The offline map
* @param peerUrgency
* The number of peers that should be in the verified map. If the number is lower, urgency is set to yes
* and we are looking for peers in the non verified map
* @param intervalSeconds
* The number of intervals to test a peer. The longer a peer is available the less often we need to check
*
*/
private DefaultMaintenance(final List<Map<Number160, PeerStatatistic>> peerMapVerified,
final List<Map<Number160, PeerStatatistic>> peerMapNonVerified,
final ConcurrentCacheMap<Number160, PeerAddress> offlineMap, final int peerUrgency,
final int[] intervalSeconds) {
this.peerMapVerified = peerMapVerified;
this.peerMapNonVerified = peerMapNonVerified;
this.offlineMap = offlineMap;
this.peerUrgency = peerUrgency;
this.intervalSeconds = intervalSeconds;
}
/**
* Constructor that initializes the maps as null references. To use this class init must be called that creates a
* new class with the private constructor.
*
* @param peerUrgency
* The number of peers that should be in the verified map. If the number is lower, urgency is set to yes
* and we are looking for peers in the non verified map
* @param intervalSeconds
* The number of intervals to test a peer. The longer a peer is available the less often we need to check
*/
public DefaultMaintenance(final int peerUrgency, final int[] intervalSeconds) {
this.peerMapVerified = null;
this.peerMapNonVerified = null;
this.offlineMap = null;
this.peerUrgency = peerUrgency;
this.intervalSeconds = intervalSeconds;
}
@Override
public Maintenance init(final List<Map<Number160, PeerStatatistic>> peerMapVerified,
final List<Map<Number160, PeerStatatistic>> peerMapNonVerified,
final ConcurrentCacheMap<Number160, PeerAddress> offlineMap) {
return new DefaultMaintenance(peerMapVerified, peerMapNonVerified, offlineMap, peerUrgency,
intervalSeconds);
}
/**
* Finds the next peer that should have a maintenance check. Returns null if no maintenance is needed at the moment.
* It will return the most important peers first. Importance is as follows: The most important peers are the close
* ones in the verified peer map. If a certain threshold in a bag is not reached, the unverified becomes important
* too.
*
* @return The next most important peer to check if its still alive.
*/
public PeerStatatistic nextForMaintenance(Collection<PeerAddress> notInterestedAddresses) {
if (peerMapVerified == null || peerMapNonVerified == null || offlineMap == null) {
throw new IllegalArgumentException("did not initialize this maintenance class");
}
int peersBefore = 0;
for (int i = 0; i < Number160.BITS; i++) {
final Map<Number160, PeerStatatistic> mapVerified = peerMapVerified.get(i);
boolean urgent = false;
synchronized (mapVerified) {
final int size = mapVerified.size();
peersBefore += size;
urgent = isUrgent(i, size, peersBefore);
}
if (urgent) {
final Map<Number160, PeerStatatistic> mapNonVerified = peerMapNonVerified.get(i);
final PeerStatatistic readyForMaintenance = next(mapNonVerified);
if (readyForMaintenance != null
&& !notInterestedAddresses.contains(readyForMaintenance.getPeerAddress())) {
LOG.debug("check peer {} from the non verified map",readyForMaintenance.getPeerAddress());
return readyForMaintenance;
}
}
final PeerStatatistic readyForMaintenance = next(mapVerified);
if (readyForMaintenance != null
&& !notInterestedAddresses.contains(readyForMaintenance.getPeerAddress())) {
return readyForMaintenance;
}
}
return null;
}
/**
* Returns a peer with its statistics from a bag that needs maintenance.
*
* @param map
* The bag with all the peers
* @return A peer that needs maintenance
*/
private PeerStatatistic next(final Map<Number160, PeerStatatistic> map) {
synchronized (map) {
for (PeerStatatistic peerStatatistic : map.values()) {
if (needMaintenance(peerStatatistic)) {
return peerStatatistic;
}
}
}
return null;
}
/**
* Indicates if it is urgent to search for a peer. This means that we have not enough peers in the verified map and
* we need to get one from the non-verified map.
*
* @param bagIndex
* The number of the bagindex. The smaller the index, the more important the peer
* @param bagSize
* The size of the current bag
* @param peersBefore
* The number of peers we have that are smaller than in this bag index
* @return True, if we need urgently a peer from the non-verified map
*/
protected boolean isUrgent(final int bagIndex, final int bagSize, final int peersBefore) {
return bagSize < peerUrgency;
}
/**
* Indicates if a peer needs a maintenance check.
*
* @param peerStatatistic
* The peer with its statistics
* @return True if the peer needs a maintenance check
*/
protected boolean needMaintenance(final PeerStatatistic peerStatatistic) {
// go for the time 5, 10, 20, 40, 80, 160
final int divide = 5;
final int online = peerStatatistic.onlineTime() / divide;
final int index;
if (online == 0) {
index = 0;
} else {
index = Math.min(intervalSeconds.length - 1, log2(online) + 1);
}
final int time = intervalSeconds[index];
final long lastTimeWhenChecked = Timings.currentTimeMillis() - peerStatatistic.getLastSeenOnline();
return lastTimeWhenChecked > TimeUnit.SECONDS.toMillis(time);
}
/**
* As seen in: http://stackoverflow.com/questions/3305059/how-do-you-calculate-log-base-2-in-java-for-integers.
*
* @param n
* The number
* @return the logarithm base 2
*/
public static int log2(final int n) {
if (n <= 0) {
throw new IllegalArgumentException();
}
return (Integer.SIZE - 1) - Integer.numberOfLeadingZeros(n);
}
}