/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package org.kevoree.library.logger.greg;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.UUID;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import org.apache.commons.math.MathException;
import org.apache.commons.math.distribution.TDistributionImpl;
import org.greg.server.LittleEndianDataInputStream;
import org.greg.server.LittleEndianDataOutputStream;
import org.greg.server.PreciseClock;
import org.greg.server.PreciseDateTime;
import org.greg.server.TimeSpan;
import org.greg.server.Trace;
import org.kevoree.annotation.*;
import org.kevoree.framework.AbstractChannelFragment;
import org.kevoree.framework.ChannelFragmentSender;
import org.kevoree.framework.message.Message;
/**
*
* @author ffouquet
*/
@Library(name = "Greg")
@ChannelTypeFragment
@DictionaryType(
@DictionaryAttribute(name = "port"))
public class GregCalibrationChannel extends AbstractChannelFragment implements Runnable {
private int maxCalibrationIters = 100;
private int minCalibrationIters = 10;
private int preCalibrationIters = 10;
private double desiredConfidenceLevel = 0.95;
private int desiredConfidenceRangeMs = 1;
Thread reception = null;
Boolean listen = true;
@Start
public void startChannel() {
reception = new Thread(this);
reception.start();
}
@Stop
public void stopChannel() {
listen = false;
}
@Override
public void run() {
ServerSocket calibrationServer = null;
try {
calibrationServer = new ServerSocket(Integer.parseInt(this.getDictionary().get("port").toString()));
} catch (IOException e) {
Trace.writeLine("Failed to create calibration listener", e);
}
Executor executor = Executors.newFixedThreadPool(16);
while (listen) {
final Socket client;
try {
client = calibrationServer.accept();
client.setTcpNoDelay(true);
} catch (Exception e) {
Trace.writeLine("Failed to accept client for calibration", e);
continue;
}
final SocketAddress ep = client.getRemoteSocketAddress();
executor.execute(new Runnable() {
public void run() {
try {
GregCalibrationMessage cmsg = processCalibrationExchange(client, ep);
//records.setAdress(client.getRemoteSocketAddress());
Message msg = new Message();
msg.setContent(cmsg);
Object result = remoteDispatch(msg);
} catch (Exception e) {
Trace.writeLine("Failed to process calibration exchange with " + ep, e);
} finally {
try {
client.close();
} catch (IOException e) {
// Ignore
}
}
}
});
}
}
@Override
public Object dispatch(Message msg) {
for (org.kevoree.framework.KevoreePort p : getBindedPorts()) {
forward(p, msg);
}
return null;
}
@Override
public ChannelFragmentSender createSender(String remoteNodeName, String remoteChannelName) {
throw new UnsupportedOperationException("Not supported yet.");
}
private GregCalibrationMessage processCalibrationExchange(Socket client, SocketAddress ep) throws IOException {
InputStream in = client.getInputStream();
OutputStream out = client.getOutputStream();
// http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#On-line_algorithm
DataOutput w = new LittleEndianDataOutputStream(out);
DataInput r = new LittleEndianDataInputStream(in);
UUID uuid = new UUID(r.readLong(), r.readLong());
// We exchange *ticks* (0.1us intervals)
// Do some iterations to warm up the TCP connection
for (int i = 0; i < preCalibrationIters; ++i) {
w.writeLong(PreciseClock.INSTANCE.now().toUtcNanos());
r.readLong();
}
long mean = 0;
long m2 = 0;
try {
for (int i = 0; i < maxCalibrationIters; ++i) {
PreciseDateTime beforeSend = PreciseClock.INSTANCE.now();
w.writeLong(beforeSend.toUtcNanos());
PreciseDateTime clientTime = new PreciseDateTime(r.readLong());
PreciseDateTime afterReceive = PreciseClock.INSTANCE.now();
long latencyNanos = (afterReceive.toUtcNanos() - beforeSend.toUtcNanos()) / 2;
long clockLatenessNanos = clientTime.toUtcNanos() - beforeSend.toUtcNanos() - latencyNanos;
// clientTime == beforeSend + clockLateness + latency
// afterReceive == clientTime - clockLateness + latency
// Trace.writeLine(
// "[" + ep + "] Iteration " + i + ": clock late by " +
// new TimeSpan(clockLateness) +
// " (beforeSend: " + beforeSend.toString() +
// ", clientTime: " + clientTime.toString() +
// ", afterReceive: " + afterReceive.toString()+ ")");
int n = i + 1;
long delta = clockLatenessNanos - mean;
mean += delta / n;
m2 += delta * (clockLatenessNanos - mean);
if (n == 1) {
continue;
}
double s = Math.sqrt(1.0 * m2 / (n - 1));
double td = new TDistributionImpl(n - 1).cumulativeProbability((1 + desiredConfidenceLevel) / 2);
double confidenceRange = 2 * s / Math.sqrt(n) * td;
// Trace.writeLine("[" + ep + "] confidence range = " + confidenceRange / 1000000 + " ms");
if (i >= minCalibrationIters && confidenceRange < desiredConfidenceRangeMs * 1000000) {
// Trace.writeLine("[" + ep + "] Achieved desired confidence range");
break;
}
}
} catch (MathException e) {
Trace.writeLine("Math exception while calibrating with " + ep, e);
}
Trace.writeLine("Clock lateness with client " + ep + " (" + uuid + ") is " + new TimeSpan(mean));
GregCalibrationMessage result = new GregCalibrationMessage();
result.setUuid(uuid);
result.setTimeSpan(new TimeSpan(mean));
return result;
// clientLateness.put(uuid, new TimeSpan(mean));
}
}