package net.i2p.router.peermanager; /* * free (adj.): unencumbered; not under the control of others * Written by jrandom in 2003 and released into the public domain * with no warranty of any kind, either expressed or implied. * It probably won't make your computer catch on fire, or eat * your children, but it might. Use at your own risk. * */ import java.util.Properties; import net.i2p.data.Hash; import net.i2p.router.ProfileManager; import net.i2p.router.RouterContext; import net.i2p.util.Log; /** * Methods to update profiles. * Unless otherwise noted, methods are blocking on the reorganize lock. */ public class ProfileManagerImpl implements ProfileManager { private final Log _log; private final RouterContext _context; public ProfileManagerImpl(RouterContext context) { _context = context; _log = _context.logManager().getLog(ProfileManagerImpl.class); } /** * Note that it took msToSend to send a message of size bytesSent to the peer over the transport. * This should only be called if the transport considered the send successful. * Non-blocking. Will not update the profile if we can't get the lock. */ public void messageSent(Hash peer, String transport, long msToSend, long bytesSent) { PeerProfile data = getProfileNonblocking(peer); if (data == null) return; data.setLastSendSuccessful(_context.clock().now()); //data.getSendSuccessSize().addData(bytesSent, msToSend); } /** * Note that the router failed to send a message to the peer over the transport specified. * Non-blocking. Will not update the profile if we can't get the lock. */ public void messageFailed(Hash peer, String transport) { PeerProfile data = getProfileNonblocking(peer); if (data == null) return; data.setLastSendFailed(_context.clock().now()); } /** * Note that the router failed to send a message to the peer over any transport. * Non-blocking. Will not update the profile if we can't get the lock. */ public void messageFailed(Hash peer) { PeerProfile data = getProfileNonblocking(peer); if (data == null) return; data.setLastSendFailed(_context.clock().now()); } /** * Note that there was some sort of communication error talking with the peer * */ public void commErrorOccurred(Hash peer) { if (_log.shouldLog(Log.INFO)) _log.info("Comm error occurred for peer " + peer.toBase64(), new Exception("Comm error")); PeerProfile data = getProfile(peer); //if (data == null) return; data.setLastSendFailed(_context.clock().now()); } /** * Note that the router agreed to participate in a tunnel * */ public void tunnelJoined(Hash peer, long responseTimeMs) { PeerProfile data = getProfile(peer); //if (data == null) return; data.getTunnelCreateResponseTime().addData(responseTimeMs, responseTimeMs); data.setLastHeardFrom(_context.clock().now()); data.getTunnelHistory().incrementAgreedTo(); } /** * Note that a router explicitly rejected joining a tunnel. * * @param responseTimeMs ignored * @param severity how much the peer doesnt want to participate in the * tunnel (large == more severe) */ public void tunnelRejected(Hash peer, long responseTimeMs, int severity) { PeerProfile data = getProfile(peer); //if (data == null) return; data.setLastHeardFrom(_context.clock().now()); data.getTunnelHistory().incrementRejected(severity); } /** * Note that a router did not respond to a tunnel join. * * Since TunnelHistory doesn't have a timeout stat, pretend we were * rejected for bandwidth reasons. */ public void tunnelTimedOut(Hash peer) { PeerProfile data = getProfile(peer); //if (data == null) return; data.getTunnelHistory().incrementRejected(TunnelHistory.TUNNEL_REJECT_BANDWIDTH); } /** * Note that a tunnel that the router is participating in * was successfully tested with the given round trip latency * */ public void tunnelTestSucceeded(Hash peer, long responseTimeMs) { PeerProfile data = getProfile(peer); //if (data == null) return; data.updateTunnelTestTimeAverage(responseTimeMs); data.getTunnelTestResponseTime().addData(responseTimeMs, responseTimeMs); } public void tunnelDataPushed(Hash peer, long rtt, int size) { if (_context.routerHash().equals(peer)) return; PeerProfile data = getProfile(peer); //if (data != null) data.dataPushed(size); // ignore rtt, as we are averaging over a minute } public void tunnelDataPushed1m(Hash peer, int size) { if (_context.routerHash().equals(peer)) return; PeerProfile data = getProfile(peer); //if (data != null) data.dataPushed1m(size); } public void tunnelLifetimePushed(Hash peer, long lifetime, long size) { if (_context.routerHash().equals(peer)) return; PeerProfile data = getProfile(peer); //if (data != null) data.tunnelDataTransferred(size); } /** * Note that the peer participated in a tunnel that failed. Its failure may not have * been the peer's fault however. * Blame the peer with a probability of pct/100. * */ public void tunnelFailed(Hash peer, int pct) { PeerProfile data = getProfile(peer); //if (data == null) return; data.setLastHeardFrom(_context.clock().now()); data.getTunnelHistory().incrementFailed(pct); } /** * Note that the peer was able to return the valid data for a db lookup * * This will force creation of DB stats */ public void dbLookupSuccessful(Hash peer, long responseTimeMs) { PeerProfile data = getProfile(peer); //if (data == null) return; data.setLastHeardFrom(_context.clock().now()); if (!data.getIsExpandedDB()) data.expandDBProfile(); data.getDbResponseTime().addData(responseTimeMs, responseTimeMs); DBHistory hist = data.getDBHistory(); hist.lookupSuccessful(); } /** * Note that the peer was unable to reply to a db lookup - either with data or with * a lookupReply redirecting the user elsewhere * * This will force creation of DB stats */ public void dbLookupFailed(Hash peer) { PeerProfile data = getProfile(peer); //if (data == null) return; if (!data.getIsExpandedDB()) data.expandDBProfile(); DBHistory hist = data.getDBHistory(); hist.lookupFailed(); } /** * Note that the peer replied to a db lookup with a redirect to other routers, where * the list of redirected users included newPeers routers that the local router didn't * know about, oldPeers routers that the local router already knew about, the given invalid * routers that were invalid in some way, and the duplicate number of routers that we explicitly * asked them not to send us, but they did anyway * */ public void dbLookupReply(Hash peer, int newPeers, int oldPeers, int invalid, int duplicate, long responseTimeMs) { PeerProfile data = getProfile(peer); //if (data == null) return; data.setLastHeardFrom(_context.clock().now()); if (!data.getIsExpandedDB()) return; data.getDbResponseTime().addData(responseTimeMs, responseTimeMs); data.getDbIntroduction().addData(newPeers, responseTimeMs); DBHistory hist = data.getDBHistory(); hist.lookupReply(newPeers, oldPeers, invalid, duplicate); } /** * Note that the local router received a db lookup from the given peer * */ public void dbLookupReceived(Hash peer) { PeerProfile data = getProfile(peer); //if (data == null) return; data.setLastHeardFrom(_context.clock().now()); if (!data.getIsExpandedDB()) return; //DBHistory hist = data.getDBHistory(); //hist.lookupReceived(); } /** * Note that the local router received an unprompted db store from the given peer * */ public void dbStoreReceived(Hash peer, boolean wasNewKey) { PeerProfile data = getProfile(peer); //if (data == null) return; data.setLastHeardFrom(_context.clock().now()); if (!data.getIsExpandedDB()) return; DBHistory hist = data.getDBHistory(); hist.unpromptedStoreReceived(wasNewKey); } /** * Note that we've confirmed a successful send of db data to the peer (though we haven't * necessarily requested it again from them, so they /might/ be lying) * * This is not really interesting, since they could be lying, so we do not * increment any DB stats at all. On verify, call dbStoreSuccessful(). * * @param responseTimeMs ignored */ public void dbStoreSent(Hash peer, long responseTimeMs) { PeerProfile data = getProfile(peer); //if (data == null) return; long now = _context.clock().now(); data.setLastHeardFrom(now); data.setLastSendSuccessful(now); //if (!data.getIsExpandedDB()) // data.expandDBProfile(); //DBHistory hist = data.getDBHistory(); //hist.storeSuccessful(); } /** * Note that we've verified a successful send of db data to the floodfill peer * by querying another floodfill. * * This will force creation of DB stats */ public void dbStoreSuccessful(Hash peer) { PeerProfile data = getProfile(peer); //if (data == null) return; long now = _context.clock().now(); data.setLastHeardFrom(now); data.setLastSendSuccessful(now); if (!data.getIsExpandedDB()) data.expandDBProfile(); DBHistory hist = data.getDBHistory(); hist.storeSuccessful(); } /** * Note that we were unable to confirm a successful send of db data to * the peer, at least not within our timeout period * * This will force creation of DB stats */ public void dbStoreFailed(Hash peer) { PeerProfile data = getProfile(peer); //if (data == null) return; if (!data.getIsExpandedDB()) data.expandDBProfile(); DBHistory hist = data.getDBHistory(); hist.storeFailed(); // we could do things like update some sort of "how many successful stores we've // failed to send them"... } /** * Note that the local router received a reference to the given peer, either * through an explicit dbStore or in a dbLookupReply */ public void heardAbout(Hash peer) { PeerProfile data = getProfile(peer); //if (data == null) return; data.setLastHeardAbout(_context.clock().now()); } /** * Note that the local router received a reference to the given peer * at a certain time. Only update the time if newer. */ public void heardAbout(Hash peer, long when) { PeerProfile data = getProfile(peer); //if (data == null) return; data.setLastHeardAbout(when); } /** * Note that the router received a message from the given peer on the specified * transport. Messages received without any "from" information aren't recorded * through this metric. If msToReceive is negative, there was no timing information * available. * Non-blocking. Will not update the profile if we can't get the lock. */ public void messageReceived(Hash peer, String style, long msToReceive, int bytesRead) { PeerProfile data = getProfileNonblocking(peer); if (data == null) return; data.setLastHeardFrom(_context.clock().now()); //data.getReceiveSize().addData(bytesRead, msToReceive); } /** * Blocking. * Creates a new profile if it didn't exist. * @return non-null */ private PeerProfile getProfile(Hash peer) { PeerProfile prof = _context.profileOrganizer().getProfile(peer); if (prof == null) { prof = new PeerProfile(_context, peer); _context.profileOrganizer().addProfile(prof); } return prof; } /** * Non-blocking. * @return null if the profile doesn't exist, or the fetch would have blocked * @since 0.8.12 */ private PeerProfile getProfileNonblocking(Hash peer) { return _context.profileOrganizer().getProfileNonblocking(peer); } /** * provide a simple summary of a number of peers, suitable for publication in the netDb * @deprecated unused */ @Deprecated public Properties summarizePeers(int numPeers) { /**** Set peers = new HashSet(numPeers); // lets get the fastest ones we've got (this fails over to include just plain reliable, // or even notFailing peers if there aren't enough fast ones) _context.profileOrganizer().selectFastPeers(numPeers, null, peers); ****/ Properties props = new Properties(); /**** for (Iterator iter = peers.iterator(); iter.hasNext(); ) { Hash peer = (Hash)iter.next(); PeerProfile prof = getProfile(peer); if (prof == null) continue; StringBuilder buf = new StringBuilder(64); buf.append("status: "); if (_context.profileOrganizer().isFast(peer)) { buf.append("fast"); } else if (_context.profileOrganizer().isHighCapacity(peer)) { buf.append("highCapacity"); } else if (_context.profileOrganizer().isFailing(peer)) { buf.append("failing"); } else { buf.append("notFailing"); } if (_context.profileOrganizer().isWellIntegrated(peer)) buf.append("Integrated "); else buf.append(" "); buf.append("capacity: ").append(num(prof.getCapacityValue())).append(" "); buf.append("speed: ").append(num(prof.getSpeedValue())).append(" "); buf.append("integration: ").append(num(prof.getIntegrationValue())); props.setProperty("profile." + peer.toBase64().replace('=', '_'), buf.toString()); } ****/ return props; } /**** private final static DecimalFormat _fmt = new DecimalFormat("##0.00", new DecimalFormatSymbols(Locale.UK)); private final static String num(double val) { synchronized (_fmt) { return _fmt.format(val); } } ****/ }