/*******************************************************************************
* Copyright (c) 2015 Institute for Pervasive Computing, ETH Zurich and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.html.
*
* Contributors:
* Matthias Kovatsch - creator and main architect
* Martin Lanter - architect and initial implementation
******************************************************************************/
package org.eclipse.californium.tools;
import java.awt.Toolkit;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Scanner;
import org.eclipse.californium.core.CoapClient;
import org.eclipse.californium.core.coap.MediaTypeRegistry;
import org.eclipse.californium.tools.coapbench.Command;
/**
* The master keeps a TCP connection to all client slaves. The master sends
* commands to all slaves. Use @1 to send a command only to client with id 1.
*/
public class ClientMaster implements Runnable {
public static final String CMD_EXIT = "exit";
public static final String CMD_STATUS = "status";
public static final String CMD_PING = "ping";
public static final String CMD_STRESS = "stress";
public static final String CMD_BENCH = "bench";
public static final String CMD_OBSERVE_BENCH = "observe";
public static final String CMD_OBSERVE_START = "observe_start";
public static final String CMD_OBSERVE_READY = "observe_ready";
public static final String CMD_OBSERVE_FAIL = "observe_fail";
public static final String CMD_WAIT = "wait";
public static final String CMD_BEEP = "beep";
public static final String CMD_APACHE_BENCH = "ab";
public static final String CMD_HELP = "help";
public static final String CMD_POST = "post";
private ServerSocket masterSocket;
private List<Slave> slaves;
private String last = "";
public ClientMaster(int port) throws Exception {
this.masterSocket = new ServerSocket(port);
this.slaves = new LinkedList<Slave>();
}
public void start() {
int successful = 0;
int totalobserves = 0;
System.out.println("Start client master");
System.out.println("Type command, e.g., \"help\":");
new Thread(this).start();
Scanner in = new Scanner(System.in);
try {
while (true) {
try {
successful = 0;
totalobserves = 0;
String line = in.nextLine();
if (line.equals("-"))
line = last;
else last = line;
String[] commands = line.split(";");
for (String cmd:commands) {
if (new Command(cmd.trim()).getBody().startsWith(CMD_OBSERVE_BENCH))
totalobserves++;
}
for (String cmd:commands) {
Command command = new Command(cmd.trim());
String body = command.getBody();
if (body.isEmpty()) {
continue;
} else if (body.startsWith(CMD_EXIT)) {
exit(command);
} else if (body.startsWith(CMD_STATUS)) {
status();
} else if (body.startsWith(CMD_PING)) {
ping(command);
} else if (body.startsWith(CMD_STRESS)) {
command(command);
} else if (body.startsWith(CMD_BENCH)) {
command(command);
} else if (body.startsWith(CMD_OBSERVE_BENCH)) {
if (observe(command))
++successful;
else {
observe_fail();
break;
}
if (successful == totalobserves)
observe_start();
} else if (body.startsWith(CMD_APACHE_BENCH)) {
command(command);
} else if (body.startsWith(CMD_WAIT)) {
wait(command);
} else if (body.startsWith(CMD_BEEP)) {
Toolkit.getDefaultToolkit().beep();
} else if (body.startsWith(CMD_POST)) {
post(command);
} else if (body.startsWith(CMD_HELP)) {
printHelp();
} else {
System.out.println("Unknown command: "+command);
}
}
System.out.println();
} catch (Exception e) {
e.printStackTrace();
}
System.out.print("Type command: ");
}
} finally { in.close(); }
}
public void status() {
System.out.println("Connected to "+slaves.size()+" slaves");
for (Slave s:slaves)
System.out.println(s);
}
public void ping(Command command) {
System.out.println("Ping to slaves");
for (Slave s:getSlaves(command.getAt())) {
System.out.println(" - "+s+": "+s.ping()+" ms");
}
}
private void command(Command command) {
for (Slave s:getSlaves(command.getAt())) {
System.out.println("Send \""+command.getBody()+"\" to "+s);
s.send(command.getBody());
}
}
private boolean observe(Command command) {
ArrayList<Slave> subslaves = getSlaves(command.getAt());
int timeout = 10000;
for (Slave slave:subslaves) {
System.out.println("Observe cmd \"" + command.getBody() + "\" sent to " + slave);
if (command.has("-log")) {
slave.send(command.getBody());
continue;
}
if (command.has("-s"))
timeout = ((250 + command.getInt("-s")) * 40 > 1000 ? (250 + command.getInt("-s")) * 40 : 1000);
if (!slave.observe_init(command, timeout)) {
if (slave.ping() < 0) {
System.err.println("Slave #" + slave.id + " is unreachable.");
try {
slave.socket.close();
} catch (IOException e) {
e.printStackTrace();
}
remove(slave);
}
return false;
}
}
return true;
}
private void observe_start() {
System.out.println("All slaves reported ready for observe benchmarking.");
for (Slave s:getSlaves(Command.ALL))
s.send(CMD_OBSERVE_START);
}
private void observe_fail() {
System.out.println("Observe benchmark fails: " + ((slaves.size() < 0) ? "there are no registered slaves left." : "not all slaves have initialized the test successfully."));
for (Slave s:getSlaves(Command.ALL))
s.send(CMD_OBSERVE_FAIL);
}
private void post(Command command) throws InterruptedException {
List<String> parameters = command.getParameters();
if (parameters.size() > 0) {
String uri = parameters.get(0);
new CoapClient(uri).post("", MediaTypeRegistry.TEXT_PLAIN);
} else {
System.out.println("You have to specify a target");
}
}
public void exit(Command command) throws Exception {
System.out.println(command);
if (command.has("-all")) {
for (Slave s:getSlaves(command.getAt())) {
System.out.println("exit "+s);
s.send(CMD_EXIT);
}
Thread.sleep(100);
} else {
System.out.println("Only master exits");
}
System.out.println("exit");
System.exit(0);
}
public void wait(Command command) throws InterruptedException {
if (command.has("-t")) {
int time = command.getInt("-t");
System.out.println("Wait "+(time / 1000f) +" s" );
Thread.sleep(time);
} else {
System.out.println("no time option \"-t X\" found");
}
}
public void run() {
System.out.println("Start masterSocket "+masterSocket.getLocalSocketAddress());
while (true) {
try {
Socket connection = masterSocket.accept();
System.out.println("Connected to new slave "+connection);
Slave slave = new Slave(connection, slaves.size() + 1);
slaves.add(slave);
} catch (Exception e) {
e.printStackTrace();
}
}
}
private ArrayList<Slave> getSlaves(int at) {
if (at == Command.ALL)
return new ArrayList<Slave>(slaves);
else {
ArrayList<Slave> s = new ArrayList<Slave>();
s.add(slaves.get(at-1));
return s;
}
}
public void remove(Slave slave) {
System.out.println("Remove slave "+slave);
slaves.remove(slave);
}
private class Slave {
private int id;
private Socket socket;
private Scanner in;
public Slave(Socket socket, int id) throws Exception {
this.socket = socket;
this.socket.setSoTimeout(0);
this.in = new Scanner(socket.getInputStream());
this.id = id;
}
public boolean send(String command) { // TODO: find a way to keep a socket up when it times out
try {
socket.getOutputStream().write(new String(command + "\n").getBytes());
socket.getOutputStream().flush();
return true;
} catch (SocketException e) {
// When slave is shutdown, we arrive here
System.out.println("Exception while sending \"" + command + "\" to "+this+": \""+e.getMessage()+"\"");
remove(this);
} catch (IOException e) {
e.printStackTrace();
remove(this);
}
return false;
}
public int ping() {
try {
socket.setSoTimeout(2000);
long t0 = System.nanoTime();
boolean succ = send(CMD_PING);
if (!succ) return -1;
in.nextLine(); // wait for response
long dt = System.nanoTime() - t0;
return (int) (dt / 1000000);
} catch (NoSuchElementException nsee) {
return -1;
} catch (Exception e) {
e.printStackTrace();
return -1;
}
}
public boolean observe_init(Command cmd, int timeout) {
send(cmd.getBody());
String response = new String();
try {
socket.setSoTimeout(timeout);
response = in.nextLine();
if (!response.equals(CMD_OBSERVE_READY))
throw new NoSuchElementException();
return true;
} catch (NoSuchElementException nsee) { // timeout/fail
System.err.println("Slave #" + id + " did not manage to initialize servers (" + response + ")");
} catch (SocketException e) {
e.printStackTrace();
}
return false;
}
@Override
public String toString() {
return socket.getRemoteSocketAddress().toString();
}
}
public void printHelp() {
System.out.println(
"Send a signal to all clients each starting 50 clients for 60 seconds with the command"
+ "\n bench -c 50 -t 60 coap://localhost:5683/fibonacci?n=20"
+ "\n"
+ "\nCreate a new log file my_name (no spaces allowed)"
+ "\n bench -new-log my_name"
+ "\n"
+ "\nInsert a log entry into log file (no spaces allowed)"
+ "\n bench -log Test_No_77"
+ "\n"
+ "\nSend a signal to all clients each starting n servers for m seconds for an observe benchmark with the command"
+ "\n observe -s n -t m coap://localhost:5683/announce"
+ "\n"
+ "\nOther commands: "
+ "\n status Print the current status"
+ "\n ping Exchange a message with each slave"
+ "\n wait -t time Wait for the spe"
+ "\n beep Give a beep sound"
+ "\n exit [-all] Exit the master and all slaves"
+ "\n"
+ "\nUse an @ to send a command only to a specific slvaes, e.g., \"@2 ping\" "
+ "\nto send a ping to slave 2."
);
}
public static void main(String[] args) throws Exception {
new ClientMaster(CoapBench.DEFAULT_MASTER_PORT).start();
}
}