URingPaxos-master/src/java/ch/usi/da/paxos/ring/ProposerRole.java
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 c A control 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());
			}
		}
	}
}
