ch.usi.da.paxos.ring.ProposerRole.java Source code

Java tutorial

Introduction

Here is the source code for ch.usi.da.paxos.ring.ProposerRole.java

Source

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.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;
    }

    /**
     * @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 && 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 / 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());
            }
        }
    }
}