package ch.usi.da.paxos.ring;
/*
* Copyright (c) 2013 Università della Svizzera italiana (USI)
*
* This file is part of URingPaxos.
*
* URingPaxos 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 3 of the License, or
* (at your option) any later version.
*
* URingPaxos 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 URingPaxos. If not, see <http://www.gnu.org/licenses/>.
*/
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import org.apache.commons.math3.random.RandomDataGenerator;
import org.apache.log4j.Logger;
import ch.usi.da.paxos.api.BatchPolicy;
import ch.usi.da.paxos.api.ConfigKey;
import ch.usi.da.paxos.api.PaxosRole;
import ch.usi.da.paxos.api.Proposer;
import ch.usi.da.paxos.message.Control;
import ch.usi.da.paxos.message.Message;
import ch.usi.da.paxos.message.MessageType;
import ch.usi.da.paxos.message.Value;
import ch.usi.da.paxos.storage.Decision;
import ch.usi.da.paxos.storage.FutureDecision;
import ch.usi.da.paxos.storage.Proposal;
/**
* Name: ProposerRole<br>
* Description: <br>
*
* Creation date: Aug 12, 2012<br>
* $Id$
*
* @author Samuel Benz benz@geoid.ch
*/
public class ProposerRole extends Role implements Proposer {
private final static Logger logger = Logger.getLogger(ProposerRole.class);
private final static Logger stats = Logger.getLogger("ch.usi.da.paxos.Stats");
private final static Logger proposallogger = Logger.getLogger("ch.usi.da.paxos.storage.Proposal");
private final RingManager ring;
private final RandomDataGenerator random = new RandomDataGenerator();;
private int concurrent_values = 20;
private ValueType value_type = ValueType.FIX;
private int value_size = 8912;
private int value_count = 900000;
private final Map<String,Proposal> proposals = new ConcurrentHashMap<String,Proposal>();
private final Map<String,FutureDecision> futures = new ConcurrentHashMap<String,FutureDecision>();
private BatchPolicy batcher;
private final BlockingQueue<Message> send_queue = new LinkedBlockingQueue<Message>();
private long send_count = 0;
private boolean test = false;
private final List<Long> latency = new ArrayList<Long>();
/**
* @param ring
*/
public ProposerRole(RingManager ring) {
this.ring = ring;
if(ring.getConfiguration().containsKey(ConfigKey.concurrent_values)){
concurrent_values = Integer.parseInt(ring.getConfiguration().get(ConfigKey.concurrent_values));
logger.info("Proposer concurrent_values: " + concurrent_values);
}
String batcher_class = "none";
if(ring.getConfiguration().containsKey(ConfigKey.batch_policy)){
batcher_class = ring.getConfiguration().get(ConfigKey.batch_policy);
}
try {
if(!batcher_class.equals("none")){
Class<?> policy = Class.forName(batcher_class);
batcher = (BatchPolicy) policy.newInstance();
}else{
batcher = null;
}
logger.info("Proposer batch policy: " + batcher);
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
batcher = null;
logger.error("Could not initilaize the batch policy!", e);
}
if(ring.getConfiguration().containsKey(ConfigKey.value_size)){
String v = ring.getConfiguration().get(ConfigKey.value_size);
if(v.toLowerCase().startsWith("int")){
value_type = ValueType.INTVALUE;
}else if(v.toLowerCase().startsWith("uni")){
value_type = ValueType.UNIFORM;
}else if(v.toLowerCase().startsWith("nor")){
value_type = ValueType.NORMAL;
}else if(v.toLowerCase().startsWith("exp")){
value_type = ValueType.EXPONENTIAL;
}else if(v.toLowerCase().startsWith("zip")){
value_type = ValueType.ZIPF;
}else{
value_type = ValueType.FIX;
value_size = Integer.parseInt(v);
logger.info("Proposer value_size: " + value_size);
}
logger.info("Proposer value_type: " + value_type);
}else{
logger.info("Proposer value_size: " + value_size);
}
if(ring.getConfiguration().containsKey(ConfigKey.value_count)){
value_count = Integer.parseInt(ring.getConfiguration().get(ConfigKey.value_count));
logger.info("Proposer value_count: " + value_count);
}
}
@Override
public void run() {
ring.getNetwork().registerCallback(this);
Thread t = new Thread(new ProposerResender(this));
t.setName("ProposerResender");
t.start();
if(batcher != null){
batcher.setProposer(this);
Thread b = new Thread(batcher);
b.setName("BatchPolicy");
b.start();
}
}
/**
* Use this method if you propose byte[] from outside!
*
* @param b A byte array which will proposed in a paxos instance
* @return A FutureDecision object on which you can wait until the value is proposed
*/
public synchronized FutureDecision propose(byte[] b){
send_count++;
Value v = new Value(System.nanoTime() + "" + ring.getNodeID(),b);
if(proposallogger.isDebugEnabled()){
proposallogger.debug(v);
}else if(proposallogger.isInfoEnabled()){
proposallogger.info(v.asString());
}
FutureDecision future = new FutureDecision();
futures.put(v.getID(),future);
Message m = new Message(0,ring.getNodeID(),PaxosRole.Leader,MessageType.Value,0,0,v);
if(batcher != null){
send_queue.add(m);
}else{
send(m);
}
return future;
}
/**
* Use this method to send control messages from outside!
*
* @param s A string which describes the subscribe command
* @return A FutureDecision object on which you can wait until the value is proposed
*/
public synchronized FutureDecision control(Control c){
send_count++;
Value v = new Value(Value.getControlID(),Control.toWire(c));
if(proposallogger.isDebugEnabled()){
proposallogger.debug(v);
}else if(proposallogger.isInfoEnabled()){
proposallogger.info(v.asString());
}
FutureDecision future = new FutureDecision();
futures.put(v.getID(),future);
Message m = new Message(0,ring.getNodeID(),PaxosRole.Leader,MessageType.Value,0,0,v);
if(batcher != null){
send_queue.add(m);
}else{
send(m);
}
return future;
}
/**
* @param m
*/
public void send(Message m){
proposals.put(m.getValue().getID(),new Proposal(m.getValue()));
ring.getNetwork().send(m); // send to all !
if(ring.getNetwork().getLearner() != null){
ring.getNetwork().getLearner().deliver(ring,m);
}
if(ring.getNetwork().getAcceptor() != null){
ring.getNetwork().getAcceptor().deliver(ring,m);
}
if(ring.getNetwork().getLeader() != null){
ring.getNetwork().getLeader().deliver(ring,m);
}
}
public void deliver(RingManager fromRing,Message m){
/*if(logger.isDebugEnabled()){
logger.debug("proposer " + ring.getNodeID() + " received " + m);
}*/
if(m.getType() == MessageType.Decision){
String ID = m.getValue().getID();
if(proposals.containsKey(ID)){
Proposal p = proposals.get(ID);
Value v = p.getValue();
if(m.getValue().equals(v)){ // compared by ID
if(v.isBatch()){
ByteBuffer buffer = ByteBuffer.wrap(v.getValue());
while(buffer.remaining() > 0){
try {
Message n = Message.fromBuffer(buffer);
set_decision(fromRing,n,n.getValue());
} catch (Exception e) {
logger.error("Proposer could not de-serialize batch message!" + e);
}
}
}else{
set_decision(fromRing,m,v);
}
}else{
logger.error("Proposer received Decision with different values for same instance " + m.getInstance() + "!");
}
proposals.remove(ID);
}
}
}
private void set_decision(RingManager fromRing,Message m,Value v){
String ID = m.getValue().getID();
if(futures.containsKey(ID)){
FutureDecision f = futures.get(ID);
f.setDecision(new Decision(fromRing.getRingID(),m.getInstance(),m.getBallot(),v));
futures.remove(ID);
}
if(test){
long time = System.nanoTime();
long send_time = Long.valueOf(ID.substring(0,ID.length()-1)); // since ID == nano-time + ring-id
long lat = time - send_time;
latency.add(lat);
if(send_count < value_count){
propose(getTestValue());
}else{
printHistogram();
test = false;
}
}
if(!test && !v.isControl() && logger.isDebugEnabled()){
long time = System.nanoTime();
long send_time = Long.valueOf(ID.substring(0,ID.length()-1)); // since ID == nano-time + ring-id
long lat = time - send_time;
logger.debug("Value " + v + " proposed and learned in " + lat + " ns (@proposer)");
}
}
/**
* @return the open proposals
*/
public Map<String, Proposal> getProposals(){
return proposals;
}
/**
* @return the ring manager
*/
public RingManager getRingManager(){
return ring;
}
/**
* @return the send (batch) queue
*/
public BlockingQueue<Message> getSendQueue(){
return send_queue;
}
public void setTestMode(){
test = true;
}
public byte[] getTestValue(){
int v = value_size;
switch(value_type){
case INTVALUE: // use for correctness test
return Integer.toString(random.nextInt(0,Integer.MAX_VALUE)).getBytes();
case NORMAL:
v = (int)random.nextGaussian(16000,14000); // tune this parameter to something meaningful
break;
case EXPONENTIAL:
v = (int)random.nextExponential(16000);
break;
case ZIPF:
v = random.nextZipf(60000,0.5); // Extremely slow?
break;
default:
v = value_size;
break;
}
if(v > 0 && v <= 60000){
return new byte[v];
}else{
return new byte[value_size];
}
}
public int getValueCount(){
return value_count;
}
public int getConcurrentValues(){
return concurrent_values;
}
private void printHistogram(){
Map<Long,Long> histogram = new HashMap<Long,Long>();
int a = 0,b = 0,b2 = 0,c = 0,d = 0,e = 0,f = 0;
long sum = 0;
for(Long l : latency){
sum = sum + l;
if(l < 1000000){ // <1ms
a++;
}else if (l < 10000000){ // <10ms
b++;
}else if (l < 25000000){ // <25ms
b2++;
}else if (l < 50000000){ // <50ms
c++;
}else if (l < 75000000){ // <75ms
f++;
}else if (l < 100000000){ // <100ms
d++;
}else{
e++;
}
Long key = new Long(Math.round(l/1000));
if(histogram.containsKey(key)){
histogram.put(key,histogram.get(key)+1);
}else{
histogram.put(key,1L);
}
}
float avg = (float)sum/latency.size()/1000/1000;
logger.info("proposer latency histogram: <1ms:" + a + " <10ms:" + b + " <25ms:" + b2 + " <50ms:" + c + " <75ms:" + f + " <100ms:" + d + " >100ms:" + e + " avg:" + avg);
if(stats.isDebugEnabled()){
for(Entry<Long, Long> bin : histogram.entrySet()){
stats.debug(bin.getKey() + "," + bin.getValue());
}
}
}
}