/*
* Copyright 2016 Netflix, Inc.
*
* 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 io.reactivex.netty.protocol.http.client.loadbalancer;
import io.reactivex.netty.client.Host;
import io.reactivex.netty.client.events.ClientEventListener;
import io.reactivex.netty.client.loadbalancer.AbstractP2CStrategy;
import io.reactivex.netty.protocol.http.client.events.HttpClientEventsListener;
import io.reactivex.netty.protocol.http.client.loadbalancer.EWMABasedP2CStrategy.HttpClientListenerImpl;
import java.util.concurrent.TimeUnit;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
public class EWMABasedP2CStrategy<W, R> extends AbstractP2CStrategy<W, R, ClientEventListener> {
private static final double STARTUP_PENALTY = Long.MAX_VALUE >> 12;
private final double tauUp;
private final double tauDown;
private double penaltyOnConnectionFailure;
private double penaltyOn503;
public EWMABasedP2CStrategy(double tauUp, double tauDown, double penaltyOnConnectionFailure,
double penaltyOn503) {
this.tauUp = tauUp;
this.tauDown = tauDown;
this.penaltyOnConnectionFailure = penaltyOnConnectionFailure;
this.penaltyOn503 = penaltyOn503;
}
public EWMABasedP2CStrategy() {
this(NANOSECONDS.convert(1, SECONDS), NANOSECONDS.convert(15, SECONDS), 2, 5);
}
@Override
protected HttpClientListenerImpl newListener(Host host) {
return new HttpClientListenerImpl();
}
@Override
protected double getWeight(ClientEventListener listener) {
return ((HttpClientListenerImpl) listener).getWeight();
}
public class HttpClientListenerImpl extends HttpClientEventsListener {
private final long epoch = System.nanoTime();
private long stamp = epoch; // last timestamp in nanos we observed an rtt
private int pending = 0; // instantaneous rate
private double cost = 0.0; // ewma of rtt, sensitive to peaks.
public double getWeight() {
observe(0.0);
if (cost == 0.0 && pending != 0) {
return STARTUP_PENALTY + pending;
} else {
return cost * (pending+1);
}
}
@Override
public synchronized void onRequestWriteComplete(long duration, TimeUnit timeUnit) {
pending += 1;
}
@Override
public synchronized void onResponseReceiveComplete(long duration, TimeUnit timeUnit) {
pending -= 1;
observe(NANOSECONDS.convert(duration, timeUnit));
}
@Override
public void onResponseHeadersReceived(int responseCode, long duration, TimeUnit timeUnit) {
if (responseCode == 503) {
observe(TimeUnit.NANOSECONDS.convert(duration, timeUnit) * penaltyOn503);
}
}
@Override
public void onConnectFailed(long duration, TimeUnit timeUnit, Throwable throwable) {
observe(TimeUnit.NANOSECONDS.convert(duration, timeUnit) * penaltyOnConnectionFailure);
}
private void observe(double rtt) {
long t = System.nanoTime();
long td = Math.max(t - stamp, 0L);
if (rtt > cost) {
double w = Math.exp(-td / tauUp);
cost = cost * w + rtt * (1.0 - w);
} else {
double w = Math.exp(-td / tauDown);
cost = cost * w + rtt * (1.0 - w);
}
stamp = t;
}
}
}