/* * This file is part of mlDHT. * * mlDHT is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * mlDHT is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with mlDHT. If not, see <http://www.gnu.org/licenses/>. */ package lbms.plugins.mldht.kad.utils; import static the8472.utils.Functional.tap; import java.util.Arrays; import java.util.Comparator; import java.util.Formatter; import lbms.plugins.mldht.kad.DHTConstants; import lbms.plugins.mldht.kad.RPCCall; import lbms.plugins.mldht.kad.RPCCallListener; import lbms.plugins.mldht.kad.messages.MessageBase; public class ResponseTimeoutFilter { public static final int NUM_SAMPLES = 256; public static final int HIGH_QUANTILE_INDEX = (int) (NUM_SAMPLES * 0.9f); public static final int LOW_QUANTILE_INDEX = (int) (NUM_SAMPLES * 0.1f); public static final int MIN_BIN = 0; public static final int MAX_BIN = DHTConstants.RPC_CALL_TIMEOUT_MAX; public static final int BIN_SIZE = 50; public static final int NUM_BINS = (int) Math.ceil((MAX_BIN - MIN_BIN) * 1.0f / BIN_SIZE); private static final Comparator<RPCCall> timeoutComp = (o1, o2) -> (int) (o1.getRTT() - o2.getRTT()); final float[] bins = new float[NUM_BINS]; volatile long updateCount; Snapshot snapshot = new Snapshot(tap(bins.clone(), ary -> {ary[ary.length-1] = 1.0f;})); long timeoutCeiling; long timeoutBaseline; public ResponseTimeoutFilter() { reset(); } /* public Map<RPCCall, Float> calculateFlightProbabilities(List<RPCCall> calls) { Collections.sort(calls,timeoutComp); long[] rtts = sortedRtts; Map<RPCCall, Float> result = new HashMap<>(calls.size()); int prevRttIdx = 0; for(RPCCall c : calls) { while(prevRttIdx < rtts.length && rtts[prevRttIdx] < c.getRTT()) prevRttIdx++; result.put(c, 1.0f * prevRttIdx / rtts.length); } return result; }*/ public void reset() { updateCount = 0; timeoutBaseline = timeoutCeiling = DHTConstants.RPC_CALL_TIMEOUT_MAX; Arrays.fill(bins, 1.0f/bins.length); } private final RPCCallListener listener = new RPCCallListener() { public void onTimeout(RPCCall c) {} public void onStall(RPCCall c) {} public void onResponse(RPCCall c, MessageBase rsp) { update(c.getRTT()); if((updateCount++ & 0x0f) == 0) { newSnapshot(); decay(); } } }; public long getSampleCount() { return updateCount; } public void registerCall(final RPCCall call) { call.addListener(listener); } void update(long newRTT) { int bin = (int) (newRTT - MIN_BIN)/BIN_SIZE; bin = Math.max(Math.min(bin, bins.length-1), 0); bins[bin] += 1.0; } void decay() { for(int i=0;i<bins.length;i++) { bins[i] *= 0.95f; } } void newSnapshot() { snapshot = new Snapshot(bins.clone()); timeoutBaseline = (long) snapshot.getQuantile(0.1f); timeoutCeiling = (long) snapshot.getQuantile(0.9f); } public Snapshot getCurrentStats() { return snapshot; } public static class Snapshot { final float[] values; float mean = 0; float mode = 0; public Snapshot(float[] ary) { values = ary; normalize(); calcStats(); } void normalize() { float cumulativePopulation = 0; for(int i=0;i<values.length;i++) { cumulativePopulation += values[i]; } if(cumulativePopulation > 0) for(int i=0;i<values.length;i++) { values[i] /= cumulativePopulation; } } void calcStats() { float modePop = 0; for(int bin=0;bin<values.length;bin++) { mean += values[bin] * (bin + 0.5f) * BIN_SIZE; if(values[bin] > modePop) { mode = (bin + 0.5f) * BIN_SIZE; modePop = values[bin]; } } } public float getQuantile(float quant) { for(int i=0;i<values.length;i++) { quant -= values[i]; if(quant <= 0) return (i + 0.5f) * BIN_SIZE; } return MAX_BIN; } @Override public String toString() { StringBuilder b = new StringBuilder(); b.append(" mean:").append(mean).append(" median:").append(getQuantile(0.5f)).append(" mode:").append(mode).append(" 10tile:").append(getQuantile(0.1f)).append(" 90tile:").append(getQuantile(0.9f)); b.append('\n'); Formatter l1 = new Formatter(); Formatter l2 = new Formatter(); for(int i=0;i<values.length;i++) { if(values[i] >= 0.001) { l1.format(" %5d | ", i*BIN_SIZE); l2.format("%5d%% | ", Math.round(values[i]*100)); } } b.append(l1).append('\n'); b.append(l2).append('\n'); return b.toString(); } } public long getStallTimeout() { // either the 90th percentile or the 10th percentile + 100ms baseline, whichever is HIGHER (to prevent descent to zero and missing more than 10% of the packets in the worst case). // but At most RPC_CALL_TIMEOUT_MAX long timeout = Math.min(Math.max(timeoutBaseline + DHTConstants.RPC_CALL_TIMEOUT_BASELINE_MIN, timeoutCeiling), DHTConstants.RPC_CALL_TIMEOUT_MAX); return timeout; } }