From 65533c0c00d3fd5b68c611ef77fa47f568d1327e Mon Sep 17 00:00:00 2001 From: fros4943 Date: Tue, 21 Aug 2007 14:39:18 +0000 Subject: [PATCH] an early version of chakana, an automated test framework used with COOJA --- tools/chakana/CYGWIN_USERS.txt | 13 + tools/chakana/__init__.py | 1 + tools/chakana/build.xml | 30 + tools/chakana/command.py | 190 +++ tools/chakana/cooja.chakana.properties | 1 + tools/chakana/cooja_plugin/README | 3 + tools/chakana/cooja_plugin/build.xml | 40 + tools/chakana/cooja_plugin/cooja.config | 2 + .../java/se/sics/chakana/ChakanaPlugin.java | 279 ++++ .../se/sics/chakana/EventpointEvaluator.java | 169 +++ .../se/sics/chakana/XMLCommandHandler.java | 1159 +++++++++++++++++ .../sics/chakana/eventpoints/Eventpoint.java | 56 + .../eventpoints/IntegerWatchpoint.java | 43 + .../eventpoints/RadioMediumEventpoint.java | 90 ++ .../chakana/eventpoints/RealTimepoint.java | 77 ++ .../eventpoints/SimulationTimepoint.java | 80 ++ .../sics/chakana/eventpoints/Timepoint.java | 38 + .../TransmissionRadioMediumEventpoint.java | 66 + .../eventpoints/VariableWatchpoint.java | 69 + .../sics/chakana/eventpoints/Watchpoint.java | 89 ++ tools/chakana/debug.py | 279 ++++ tools/chakana/error.py | 89 ++ tools/chakana/event.py | 175 +++ tools/chakana/harness.py | 126 ++ tools/chakana/linux.py | 268 ++++ tools/chakana/monitor.py | 184 +++ tools/chakana/shepherd.py | 344 +++++ tools/chakana/threads.py | 287 ++++ tools/chakana/utils.py | 736 +++++++++++ 29 files changed, 4983 insertions(+) create mode 100644 tools/chakana/CYGWIN_USERS.txt create mode 100644 tools/chakana/__init__.py create mode 100644 tools/chakana/build.xml create mode 100644 tools/chakana/command.py create mode 100644 tools/chakana/cooja.chakana.properties create mode 100644 tools/chakana/cooja_plugin/README create mode 100644 tools/chakana/cooja_plugin/build.xml create mode 100644 tools/chakana/cooja_plugin/cooja.config create mode 100644 tools/chakana/cooja_plugin/java/se/sics/chakana/ChakanaPlugin.java create mode 100644 tools/chakana/cooja_plugin/java/se/sics/chakana/EventpointEvaluator.java create mode 100644 tools/chakana/cooja_plugin/java/se/sics/chakana/XMLCommandHandler.java create mode 100644 tools/chakana/cooja_plugin/java/se/sics/chakana/eventpoints/Eventpoint.java create mode 100644 tools/chakana/cooja_plugin/java/se/sics/chakana/eventpoints/IntegerWatchpoint.java create mode 100644 tools/chakana/cooja_plugin/java/se/sics/chakana/eventpoints/RadioMediumEventpoint.java create mode 100644 tools/chakana/cooja_plugin/java/se/sics/chakana/eventpoints/RealTimepoint.java create mode 100644 tools/chakana/cooja_plugin/java/se/sics/chakana/eventpoints/SimulationTimepoint.java create mode 100644 tools/chakana/cooja_plugin/java/se/sics/chakana/eventpoints/Timepoint.java create mode 100644 tools/chakana/cooja_plugin/java/se/sics/chakana/eventpoints/TransmissionRadioMediumEventpoint.java create mode 100644 tools/chakana/cooja_plugin/java/se/sics/chakana/eventpoints/VariableWatchpoint.java create mode 100644 tools/chakana/cooja_plugin/java/se/sics/chakana/eventpoints/Watchpoint.java create mode 100644 tools/chakana/debug.py create mode 100644 tools/chakana/error.py create mode 100644 tools/chakana/event.py create mode 100644 tools/chakana/harness.py create mode 100644 tools/chakana/linux.py create mode 100644 tools/chakana/monitor.py create mode 100644 tools/chakana/shepherd.py create mode 100644 tools/chakana/threads.py create mode 100644 tools/chakana/utils.py diff --git a/tools/chakana/CYGWIN_USERS.txt b/tools/chakana/CYGWIN_USERS.txt new file mode 100644 index 000000000..62801a929 --- /dev/null +++ b/tools/chakana/CYGWIN_USERS.txt @@ -0,0 +1,13 @@ +If you're on Cygwin and run into the 'sem_init: Resource temporarily unavailable' problem: + +Current Cygwin python versions have problems with running out of PIDs, it is a known problem and there has been several discussions and bugreports related to it. +As of today, the best solution seems to be to rebase the Cygwin libraries: + 0> Make sure you have the rebaseall program, it can be downloaded using Cygwin's setup.exe + + 1> Shut down all Cygwin processes and services (including shells) + 2> Start a regular Win command prompt ('cmd') + 3> Start 'ash' + 4> Start '/bin/rebaseall', expect this step to take some time.. + 5> All done! This worked for me. + + -- Fredrik , 2007 \ No newline at end of file diff --git a/tools/chakana/__init__.py b/tools/chakana/__init__.py new file mode 100644 index 000000000..51f3cfdfc --- /dev/null +++ b/tools/chakana/__init__.py @@ -0,0 +1 @@ +"""Chakana, an automated test harness for networked embedded systems.""" diff --git a/tools/chakana/build.xml b/tools/chakana/build.xml new file mode 100644 index 000000000..e024611aa --- /dev/null +++ b/tools/chakana/build.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/chakana/command.py b/tools/chakana/command.py new file mode 100644 index 000000000..1ee9b60a2 --- /dev/null +++ b/tools/chakana/command.py @@ -0,0 +1,190 @@ +# +# Copyright 2005-2007 Swedish Institute of Computer Science. +# +# Please refer to the file named LICENSE in the same directory as this file +# for licensing information. +# +# Written and maintained by Lars Albertsson . +# + +# $Id: command.py,v 1.1 2007/08/21 14:41:01 fros4943 Exp $ + +"""Utility routines for running external commands.""" + +import errno +import os +import popen2 +import re +import signal +import threading + +import chakana.error +import chakana.linux +import chakana.threads + +def quote(argument, useTick = 1): + """Quote an argument, or a list of arguments, in order to pass it + through a shell.""" + if type(argument) in (type([]), type(())): + return " ".join(map(quote, argument)) + if useTick and not "'" in argument: + return "'" + argument + "'" + return '"' + argument.replace('\\', '\\\\').replace('"', '\\"').replace( + '`', '\\`').replace('$', '\\$') + '"' + +def softQuote(argument): + """Quote an argument, or a list of arguments, in order to pass it through + a shell if it is necessary. Always quotes with quotation mark, never + with tick.""" + if type(argument) in (type([]), type(())): + return " ".join(map(softQuote, argument)) + if re.match(r'^[\w,./=-]*$', argument): + return argument + else: + return quote(argument, useTick = 0) + +def deQuote(argument): + """Remove quoting in the same manner as the shell (bash) does.""" + if argument == "": + return "" + if argument[0] == "'": + nextQuote = argument.find("'", 1) + if nextQuote == -1: + raise ValueError("Unmatched quote \"'\"") + return argument[1 : nextQuote] + deQuote(argument[nextQuote + 1 :]) + if len(argument) > 1 and argument[-1] == "'": + return argument[1:-1] + elif argument[0] == '"': + ret = [] + index = 1 + try: + while True: + if argument[index] == '"': + return "".join(ret) + deQuote(argument[index + 1 :]) + if argument[index] == '\\': + index += 1 + if argument[index] != '\n': + ret.append(argument[index]) + else: + ret.append(argument[index]) + index += 1 + except IndexError: + raise ValueError("Unmatched quote '\"'") + elif argument[0] == '\\': + if len(argument) == 1: + return argument[0] + if argument[1] != '\n': + return argument[1] + deQuote(argument[2:]) + else: + return argument[2:] + return argument[0] + deQuote(argument[1:]) + + +def runString(argv, environmentVars = ("PYTHONPATH", )): + """Return a quoted string that can be pasted into a shell in order + to rerun a command.""" + + envPrefix = "" + for envVar in environmentVars: + envPrefix += envVar + "=" + os.environ.get(envVar, "") + " " + return "( cd " + os.getcwd() + " && " + envPrefix + quote(argv) + " )" + +class Reader: + def __call__(self, fileObj): + self._data = [] + while 1: + newData = fileObj.read() + if newData == "": + return + self._data.append(newData) + + def result(self): + return "".join(self._data) + +class Writer: + def __init__(self, fileObj, data): + self._fileObj = fileObj + self._data = data + if self._data == "": + self._fileObj.close() + self._threads = [] + else: + self._threads = [threading.Thread(target = self.write)] + self._threads[0].start() + + def write(self): + self._fileObj.write(self._data) + self._fileObj.close() + + def activeThreads(self): + return self._threads + +class Runner: + def __init__(self, child, timeout): + self._child = child + self._timeout = timeout + self._threads = [] + self._output = [] + if self._timeout is None: + self.read() + self.wait() + else: + self._threads.append(threading.Thread(target = self.read)) + self._threads[-1].start() + self._threads.append(threading.Thread(target = self.wait)) + self._threads[-1].start() + + def activeThreads(self): + return self._threads + + def read(self): + while 1: + newData = self._child.fromchild.read() + if newData == "": + return + self._output.append(newData) + + def wait(self): + self._status = self._child.wait() + + def output(self): + return "".join(self._output) + + def status(self): + return self._status + +def output(command, inputData = "", timeout = None): + """Run command in a separate subprocess. Send inputData to stdin. + Kill subprocess and raise error.Timeout after timeout seconds, + unless timeout is None.""" + + # Due to python bug 1183780 (Popen4.wait not thread-safe), avoid + # threads if we don't need them. + + child = popen2.Popen4(command) + writer = Writer(child.tochild, inputData) + runner = Runner(child, timeout) + + try: + chakana.threads.waitForAll(writer.activeThreads() + runner.activeThreads(), + timeout) + except chakana.error.Timeout, timeoutErr: + try: + childProc = chakana.linux.Process(child.pid) + childProc.killAllBelow() + childProc.reallyKill() + except OSError, err: + if not err.errno in (errno.ESRCH, errno.ECHILD): + raise + raise chakana.error.CommandFailed(command, timeoutErr, runner.output()) + + if runner.status() != 0: + raise chakana.error.CommandFailed( + command, runner.status(), runner.output()) + if re.search(r"\blibefence", os.environ.get("LD_PRELOAD", "")): + lines = runner.output().splitlines() + if (len(lines) >= 2) and (lines[0] == "") and \ + lines[1].strip().startswith("Electric Fence "): + return "\n".join(lines[2:] + [""]) + return runner.output() + diff --git a/tools/chakana/cooja.chakana.properties b/tools/chakana/cooja.chakana.properties new file mode 100644 index 000000000..22a543d7f --- /dev/null +++ b/tools/chakana/cooja.chakana.properties @@ -0,0 +1 @@ +DEFAULT_PROJECTDIRS=../cooja_plugin diff --git a/tools/chakana/cooja_plugin/README b/tools/chakana/cooja_plugin/README new file mode 100644 index 000000000..546b92f3b --- /dev/null +++ b/tools/chakana/cooja_plugin/README @@ -0,0 +1,3 @@ +Main Chakana COOJA plugin. + +Communicates with Chakana, and should automatically be started with the simulator. diff --git a/tools/chakana/cooja_plugin/build.xml b/tools/chakana/cooja_plugin/build.xml new file mode 100644 index 000000000..f3cdb2394 --- /dev/null +++ b/tools/chakana/cooja_plugin/build.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/chakana/cooja_plugin/cooja.config b/tools/chakana/cooja_plugin/cooja.config new file mode 100644 index 000000000..882383efc --- /dev/null +++ b/tools/chakana/cooja_plugin/cooja.config @@ -0,0 +1,2 @@ +se.sics.cooja.GUI.PLUGINS = + se.sics.chakana.ChakanaPlugin +se.sics.cooja.GUI.JARFILES = + chakana.jar diff --git a/tools/chakana/cooja_plugin/java/se/sics/chakana/ChakanaPlugin.java b/tools/chakana/cooja_plugin/java/se/sics/chakana/ChakanaPlugin.java new file mode 100644 index 000000000..38d4867cc --- /dev/null +++ b/tools/chakana/cooja_plugin/java/se/sics/chakana/ChakanaPlugin.java @@ -0,0 +1,279 @@ +/* + * Copyright (c) 2007, Swedish Institute of Computer Science. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of the + * Institute nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $Id: ChakanaPlugin.java,v 1.1 2007/08/21 14:39:58 fros4943 Exp $ + */ + +package se.sics.chakana; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.Collection; +import javax.swing.SwingUtilities; +import org.apache.log4j.Logger; +import org.jdom.Element; + +import se.sics.cooja.*; + +/** + * Main Chakana plugin. + * + * Provides remote control functionality to COOJA via socket connections. + * + * @see #SERVER_PORT + * @author Fredrik Osterlind + */ +@ClassDescription("Chakana COOJA Server") +@PluginType(PluginType.COOJA_STANDARD_PLUGIN) +public class ChakanaPlugin implements Plugin { + public static final String VERSION = "0.1"; + + /** + * COOJA's Chakana-plugin server port + */ + public static final int SERVER_PORT = 1234; + + private static final long serialVersionUID = 1L; + + private static Logger logger = Logger.getLogger(ChakanaPlugin.class); + + private GUI myGUI; + + // Chakana communication + private Thread serverThread = null; + + private ServerSocket serverSocket = null; + + private Thread clientThread = null; + + private Socket clientSocket = null; + + private EventpointEvaluator myEvaluator = null; + + private XMLCommandHandler myCommandHandler = null; + + private boolean shutdown = false; + + private Object tag = null; + + /** + * Creates new Chakana remote control plugin. + * + * @param gui COOJA Simulator + */ + public ChakanaPlugin(GUI gui) { + myGUI = gui; + + // Create eventpoint evaluator + myEvaluator = new EventpointEvaluator(myGUI); + + // Create remote command handler + myCommandHandler = new XMLCommandHandler(myGUI, this, myEvaluator); + + // Open server socket + serverThread = new Thread(new Runnable() { + public void run() { + try { + serverSocket = new ServerSocket(SERVER_PORT); + logger.info("Chakana server listening on port " + SERVER_PORT + "."); + } catch (Exception e) { + logger.fatal("Could not start server thread: " + e.getMessage()); + return; + } + + // Handle incoming connections + while (serverSocket != null) { + try { + Socket skt = serverSocket.accept(); + handleNewConnection(skt); + } catch (Exception e) { + if (!shutdown) + logger.fatal("Server thread exception: " + e.getMessage()); + if (serverThread != null && serverThread.isInterrupted()) { + serverThread = null; + } + serverSocket = null; + } + } + logger.info("Chakana server thread terminating"); + } + }, "chakana listen thread"); + serverThread.start(); + } + + public void performCOOJAShutdown() { + shutdown = true; + } + + /** + * Handles incoming connection. + * + * @param socket Socket + */ + private void handleNewConnection(Socket socket) { + logger.info("Received request from " + socket.getInetAddress() + ":" + + socket.getPort()); + + if (clientThread != null) { + // Refuse connection + logger.warn("A client is already connected, refusing new connection"); + try { + socket.close(); + } catch (IOException e) { + } + return; + } + + // Start thread handling connection + clientSocket = socket; + clientThread = new Thread(new Runnable() { + public void run() { + try { + // Open stream + PrintWriter writer = new PrintWriter(clientSocket.getOutputStream()); + BufferedReader reader = new BufferedReader(new InputStreamReader(clientSocket + .getInputStream())); + + // Send welcome message + writer.print(XMLCommandHandler.createInfoMessage("Chakana COOJA plugin, version " + VERSION) + "\n"); + writer.flush(); + + // Handle incoming data (blocks until connection terminated) + String line; + String command = ""; + while ((!shutdown) && ((line = reader.readLine()) != null)) { + logger.debug("<-- " + line); + command += line; + + if (myCommandHandler.containsEntireCommand(command)) { + String reply = myCommandHandler.handleCommand(command); + logger.debug("--> " + reply); + writer.print(reply + "\n"); + writer.flush(); + command = ""; + } + } + + logger.debug("Terminating Chakana connection"); + + // Clean up connection + if (writer != null) { + writer.flush(); + writer.close(); + } + if (clientSocket != null && !clientSocket.isClosed()) + clientSocket.close(); + + } catch (IOException e) { + logger.fatal("Client connection exception: " + e); + } + + clientThread = null; + clientSocket = null; + SwingUtilities.invokeLater(new Runnable() { + public void run() { + myGUI.doQuit(false); + } + }); + logger.debug("Chakana client thread terminating"); + } + }, "chakana client thread"); + clientThread.start(); + + } + + /** + * Shut down any open server socket, and kill server thread. + */ + protected void closeServer() { + // Shut down server + if (serverSocket != null && !serverSocket.isClosed()) { + logger.info("Closing server socket"); + try { + serverSocket.close(); + } catch (IOException e) { + logger.fatal("Error when closing server socket: " + e); + } + serverSocket = null; + } + + if (serverThread != null && serverThread.isAlive()) { + logger.info("Interrupting server thread"); + serverThread.interrupt(); + serverThread = null; + } + } + + /** + * Disconnect all connected clients. Currently only one client can be + * connected at the same time. + */ + protected void disconnectClients() { + // Disconnect any current accepted client + if (clientSocket != null && !clientSocket.isClosed()) { + logger.info("Closing client socket"); + try { + clientSocket.close(); + } catch (IOException e) { + logger.fatal("Error when closing client socket: " + e); + } + clientSocket = null; + } + + if (clientThread != null && clientThread.isAlive()) { + logger.info("Interrupting client thread"); + clientThread.interrupt(); + clientThread = null; + } + } + + public void closePlugin() { + closeServer(); + disconnectClients(); + } + + public void tagWithObject(Object tag) { + this.tag = tag; + } + + public Object getTag() { + return tag; + } + + public Collection getConfigXML() { + return null; + } + + public boolean setConfigXML(Collection configXML, + boolean visAvailable) { + return false; + } + +} diff --git a/tools/chakana/cooja_plugin/java/se/sics/chakana/EventpointEvaluator.java b/tools/chakana/cooja_plugin/java/se/sics/chakana/EventpointEvaluator.java new file mode 100644 index 000000000..f3e63c753 --- /dev/null +++ b/tools/chakana/cooja_plugin/java/se/sics/chakana/EventpointEvaluator.java @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2007, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: EventpointEvaluator.java,v 1.1 2007/08/21 14:39:58 fros4943 Exp $ + */ + +package se.sics.chakana; + +import java.util.Observable; +import java.util.Observer; +import java.util.Vector; +import org.apache.log4j.Logger; + +import se.sics.cooja.GUI; +import se.sics.cooja.Simulation; +import se.sics.chakana.eventpoints.*; + +/** + * Keeps track of all active eventpoints. The control method blocks until any of + * the active eventpoints trigger. + * + * @author Fredrik Osterlind + */ +public class EventpointEvaluator { + private static Logger logger = Logger.getLogger(EventpointEvaluator.class); + + private GUI myGUI; + private Vector allEventpoints; + private Eventpoint lastTriggeredEventpoint; + private Simulation sim = null; + + private int counterEventpointID = 0; + + private Observer tickObserver = new Observer() { + public void update(Observable obs, Object obj) { + // Evaluate active eventpoints + for (Eventpoint eventpoint: allEventpoints) { + if (eventpoint.evaluate()) { + lastTriggeredEventpoint = eventpoint; + sim.stopSimulation(); + logger.info("Eventpoint triggered: " + eventpoint); + } + } + } + }; + + /** + * Creates a new eventpoint evaluator. + * + * @param gui GUI with simulation + */ + public EventpointEvaluator(GUI gui) { + myGUI = gui; + allEventpoints = new Vector(); + } + + + /** + * Blocks until an active eventpoint triggers. + * + * @return Triggered eventpoint + * @throws EventpointException At evenpoint setup errors + */ + public Eventpoint resumeSimulation() throws EventpointException { + if (myGUI.getSimulation() == null) + throw new EventpointException("No simulation to observe"); + if (allEventpoints == null || allEventpoints.isEmpty()) + throw new EventpointException("No eventpoints exist"); + + // Make sure tick observer is observing the current simulation + myGUI.getSimulation().deleteTickObserver(tickObserver); + myGUI.getSimulation().addTickObserver(tickObserver); + + // Evaluate active eventpoints before starting simulation + for (Eventpoint eventpoint: allEventpoints) { + if (eventpoint.evaluate()) { + lastTriggeredEventpoint = eventpoint; + logger.info("Eventpoint triggered (EARLY): " + eventpoint); + return lastTriggeredEventpoint; + } + } + + // Reset last triggered eventpoint and start simulation + lastTriggeredEventpoint = null; + sim = myGUI.getSimulation(); + sim.startSimulation(); + + // Block until tickobserver stops simulation + while (lastTriggeredEventpoint == null || myGUI.getSimulation().isRunning()) { + Thread.yield(); + } + + if (lastTriggeredEventpoint == null) + throw new EventpointException("Simulation was stopped without eventpoint triggering"); + + return lastTriggeredEventpoint; + } + + public Eventpoint getTriggeredEventpoint() { + return lastTriggeredEventpoint; + } + + public int getLastTriggeredEventpointID() { + return lastTriggeredEventpoint.getID(); + } + + public Eventpoint getEventpoint(int id) { + for (Eventpoint eventpoint: allEventpoints) { + if (eventpoint.getID() == id) { + return eventpoint; + } + } + return null; + } + + public void addEventpoint(Eventpoint eventpoint) { + eventpoint.setID(counterEventpointID++); + allEventpoints.add(eventpoint); + } + + public void clearAllEventpoints() { + allEventpoints.clear(); + } + + public void deleteEventpoint(int id) { + Eventpoint eventpointToDelete = null; + for (Eventpoint eventpoint: allEventpoints) { + if (eventpoint.getID() == id) { + eventpointToDelete = eventpoint; + break; + } + } + + if (eventpointToDelete != null) + allEventpoints.remove(eventpointToDelete); + } + + class EventpointException extends Exception { + public EventpointException(String message) { + super(message); + } + } + +} diff --git a/tools/chakana/cooja_plugin/java/se/sics/chakana/XMLCommandHandler.java b/tools/chakana/cooja_plugin/java/se/sics/chakana/XMLCommandHandler.java new file mode 100644 index 000000000..6ce2a1cf5 --- /dev/null +++ b/tools/chakana/cooja_plugin/java/se/sics/chakana/XMLCommandHandler.java @@ -0,0 +1,1159 @@ +/* + * Copyright (c) 2007, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: XMLCommandHandler.java,v 1.1 2007/08/21 14:39:58 fros4943 Exp $ + */ + +package se.sics.chakana; + +import java.io.File; +import java.io.IOException; +import java.io.StringReader; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.util.Collection; +import javax.swing.JFrame; +import javax.swing.JInternalFrame; + +import org.apache.log4j.Logger; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.JDOMException; +import org.jdom.input.SAXBuilder; + +import se.sics.chakana.EventpointEvaluator.EventpointException; +import se.sics.chakana.eventpoints.*; +import se.sics.cooja.AddressMemory; +import se.sics.cooja.CoreComm; +import se.sics.cooja.GUI; +import se.sics.cooja.Mote; +import se.sics.cooja.MoteMemory; +import se.sics.cooja.RadioMedium; +import se.sics.cooja.Simulation; +import se.sics.cooja.Mote.State; +import se.sics.cooja.MoteType.MoteTypeCreationException; +import se.sics.cooja.dialogs.MessageList; +import se.sics.cooja.plugins.VisState; +import se.sics.cooja.plugins.Visualizer2D; +import se.sics.cooja.radiomediums.UDGM; + +/** + * Handles incoming XML commands. + * Each command handling call blocks during handling and returns a reply to the client. + * + * A command must always be of the form: + * ... + * + * @author Fredrik Osterlind + */ +public class XMLCommandHandler { + public static final boolean DEBUG_OUTPUT = true; + + private static Logger logger = Logger.getLogger(XMLCommandHandler.class); + + public static final String XML_OK = ""; + public static final String XML_OK_START = ""; + public static final String XML_OK_END = ""; + + public static final String XML_INFO_START = ""; + public static final String XML_INFO_END = ""; + public static final String XML_ERROR_START = ""; + public static final String XML_ERROR_END = ""; + + public static final String XML_COMMAND_NAME = "command"; + public static final String XML_COMMAND_END = ""; + + public static final String XML_EVENTPOINT_START = ""; + public static final String XML_EVENTPOINT_END = ""; + + public static final String XML_ID_NAME = "id"; + public static final String XML_TYPE_NAME = "type"; + public static final String XML_MOTE_NAME = "mote"; + public static final String XML_VARIABLE_NAME = "variable"; + public static final String XML_MOTECOUNT_NAME = "motecount"; + public static final String XML_VALUE_NAME = "value"; + public static final String XML_TIME_NAME = "time"; + public static final String XML_SIZE_NAME = "size"; + public static final String XML_ADDRESS_NAME = "address"; + public static final String XML_VISIBLE_NAME = "visible"; + public static final String XML_TRIGGERON_NAME = "triggeron"; + public static final String XML_WATCHPOINT_INT = "int"; + public static final String XML_WATCHPOINT_VARIABLE = "variable"; + public static final String XML_WATCHPOINT_ADDRESS = "address"; + public static final String XML_TIMEPOINT_SIMULATION = "time"; + public static final String XML_TIMEPOINT_REAL = "realtime"; + public static final String XML_EVENTPOINT_RADIOMEDIUM = "radiomedium"; + + private GUI myGUI; + private EventpointEvaluator myEvaluator; + private ChakanaPlugin myParent; + + public enum Command { + CREATE_SIM, + CONF_SIM, + CONF_PLUGINS, + CONTROL_SIM, + SET_GUI, + ADD_EVENTPOINT, + READ_MEMORY, + WRITE_MEMORY, + CLEAR_EVENTPOINTS, + DELETE_EVENTPOINT, + KILL_NODES, + GET_INFO, + CUSTOM_COMMAND, + EXIT_COOJA; + } + + public enum ControlSimulationCommand { + RESUME; + } + + /** + * TODO Document + * + * @param gui + * @param parent + * @param evaluator + */ + public XMLCommandHandler(GUI gui, ChakanaPlugin parent, EventpointEvaluator evaluator) { + myGUI = gui; + myEvaluator = evaluator; + myParent = parent; + } + + /** + * @param command + * @return + */ + public boolean containsEntireCommand(String command) { + return command.contains(XMLCommandHandler.XML_COMMAND_END); // TODO Ugly trick + } + + /** + * Handle given command (XML format). + * Method blocks until given command has been handled. + * + * @param command + * Command to handle + * @return Reply to client in XML format + */ + public String handleCommand(String command) { + try { + SAXBuilder builder = new SAXBuilder(); + Document doc = builder.build(new StringReader(command)); + Element root = doc.getRootElement(); + + if (!root.getName().equals(XML_COMMAND_NAME)) { + return createErrorMessage("Malformed command: " + root.getName()); + } + + String cmdTypeString = root.getAttributeValue("value"); + Command cmdType; + try { + cmdType = Command.valueOf(cmdTypeString); + } catch (IllegalArgumentException e) { + return createErrorMessage("Unknown command: " + cmdTypeString); + } + + Collection cmdInfo = root.getChildren(); + + logger.debug("Command type: " + cmdType); + switch (cmdType) { + case CREATE_SIM: + return createSimulation(cmdInfo); + case CONF_SIM: + return configureSimulation(cmdInfo); + case CONF_PLUGINS: + return configurePlugins(cmdInfo); + case CONTROL_SIM: + return controlSimulation(cmdInfo); + case ADD_EVENTPOINT: + return addEventpoint(cmdInfo); + case SET_GUI: + return configureGUI(cmdInfo); + case READ_MEMORY: + return readMemory(cmdInfo); + case WRITE_MEMORY: + return writeMemory(cmdInfo); + case CLEAR_EVENTPOINTS: + return clearEventpoints(); + case DELETE_EVENTPOINT: + return deleteEventpoint(cmdInfo); + case KILL_NODES: + return killNodes(cmdInfo); + case GET_INFO: + return getInfo(cmdInfo); + case CUSTOM_COMMAND: + return handleCustomCommand(cmdInfo); + case EXIT_COOJA: + return shutdownCOOJA(cmdInfo); + default: + return createErrorMessage("Unknown command: " + cmdType); + } + } catch (JDOMException e) { + logger.fatal("Command parsing exception: " + e); + return createErrorMessage("Invalid command syntax: " + e.getMessage()); + } catch (IOException e) { + logger.fatal("Command parsing exception: " + e); + return createErrorMessage("Invalid command syntax: " + e.getMessage()); + } + } + + /** + * Handle create new simulation command. + * + * @param arguments Command arguments + * @return Reply to client (XML format) + */ + private String createSimulation(Collection arguments) { + Simulation simulation = new Simulation(myGUI); + simulation.setTitle("[chakana - no title]"); + simulation.setDelayTime(0); + simulation.setSimulationTime(0); + simulation.setTickTime(1); + + try { + RadioMedium radioMedium = RadioMedium.generateRadioMedium(UDGM.class, + simulation); + simulation.setRadioMedium(radioMedium); + } catch (Exception e) { + return createErrorMessage("Create simulation: Could not create radio medium: " + e.getMessage()); + } + + // Let simulation parse command arguments + myGUI.setSimulation(simulation); + try { + boolean success = simulation.setConfigXML(arguments, false); + } catch (Exception e) { + logger.fatal("Error when configuring simulation: " + e); + if (DEBUG_OUTPUT) { + if (e instanceof MoteTypeCreationException) { + MessageList compilationOutput = ((MoteTypeCreationException) e).getCompilationOutput(); + if (compilationOutput != null) { + logger.info("Compilation output:"); + for(int i = 0; i < compilationOutput.getModel().getSize(); i++) { + logger.info(compilationOutput.getModel().getElementAt(i)); + } + } + StackTraceElement[] stackTrace = e.getStackTrace(); + if (stackTrace != null) { + logger.info("Stack trace:"); + for(StackTraceElement element: stackTrace) { + logger.info(element); + } + } + GUI.showErrorDialog(new JFrame(""), "Set DEBUG_OUTPUT to false to disable this frame", e, false); // XXX Graphical component + } + } + return createErrorMessage("Create simulation: Could not configure simulation: " + e.getMessage()); + } + + return XML_OK; + } + + /** + * Handle configure simulation command. + * + * @param arguments Command arguments + * @return Reply to client (XML format) + */ + private String configureSimulation(Collection arguments) { + Simulation simulation = myGUI.getSimulation(); + + // Let simulation parse command arguments + try { + simulation.setConfigXML(arguments, false); + } catch (Exception e) { + logger.fatal("Error when configuring simulation: " + e); + e.printStackTrace(); + return createErrorMessage("Could not configure simulation: " + e.getMessage()); + } + return XML_OK; + } + + /** + * Handle configure plugins command. + * + * @param arguments Command arguments + * @return Reply to client (XML format) + */ + private String configurePlugins(Collection arguments) { + Simulation simulation = myGUI.getSimulation(); + + // Let GUI parse command arguments + try { + myGUI.setPluginsConfigXML(arguments, simulation, false); + } catch (Exception e) { + logger.fatal("Error when configuring plugins: " + e); + return createErrorMessage("Could not configure plugins: " + e.getMessage()); + } + + return XML_OK; + } + + /** + * Handle control simulation command. + * + * @param arguments Command arguments + * @return Reply to client (XML format) + */ + private String controlSimulation(Collection arguments) { + String reply = ""; + + for (Element element : arguments) { + ControlSimulationCommand cmdType; + try { + cmdType = ControlSimulationCommand.valueOf(element.getName()); + } catch (IllegalArgumentException e) { + return createErrorMessage("Unknown simulation control command: " + element.getName()); + } + + switch (cmdType) { + case RESUME: + // TODO Check performance degradation using try-blocks + try { + myEvaluator.resumeSimulation(); + reply += createEventpointMessage(myEvaluator); + } catch (EventpointException e) { + return createErrorMessage("Exception during simulation: " + e); + } + break; + default: + return createErrorMessage("Unknown simulation control command"); + } + } + return reply; + } + + /** + * Handle exit COOJA command. + * + * @param arguments Command arguments + * @return Reply to client (XML format) + */ + private String shutdownCOOJA(Collection arguments) { + myParent.performCOOJAShutdown(); + logger.info("Shutdown requested"); + return XML_OK; + } + + /** + * Fetch mote variable value as specified by arguments. + * + * @param arguments Command arguments + * @return Reply to client (XML format) + */ + private String readMemory(Collection arguments) { + Simulation simulation = myGUI.getSimulation(); + + String type = null; + String mote = null; + String variable = null; + String size = null; + String address = null; + + for (Element element : arguments) { + if (element.getName().equals(XML_TYPE_NAME)) { + type = element.getText(); + } else if (element.getName().equals(XML_MOTE_NAME)) { + mote = element.getText(); + } else if (element.getName().equals(XML_VARIABLE_NAME)) { + variable = element.getText(); + } else if (element.getName().equals(XML_SIZE_NAME)) { + size = element.getText(); + } else if (element.getName().equals(XML_ADDRESS_NAME)) { + address = element.getText(); + } else { + return createErrorMessage("Unknown read memory parameter: " + element.getName()); + } + } + + if (type == null) + return createErrorMessage("No read memory type specified"); + if (mote == null) + return createErrorMessage("No mote specified"); + + if (mote == null) + return createErrorMessage("No mote ID specified"); + int moteNr = Integer.parseInt(mote); + if (moteNr < 0 || simulation.getMotesCount() <= moteNr) { + return createErrorMessage("Bad mote ID specified: " + moteNr); + } + MoteMemory memory = simulation.getMote(moteNr).getMemory(); + + // Read integer variable + if (type.equals("int")) { + if (variable == null) + return createErrorMessage("No variable name specified"); + if (variable.contains(" ")) + return createErrorMessage("Variable name must not contain spaces: " + variable); + + if (!(memory instanceof AddressMemory)) + return createErrorMessage("Can't read mote memory variables (not address memory)"); + + if (!((AddressMemory) memory).variableExists(variable)) { + return createErrorMessage("Variable does not exist: " + variable); + } + + int val = ((AddressMemory) memory).getIntValueOf(variable); + return XML_OK_START + val + XML_OK_END; + } else if (type.equals("variable")) { + if (variable == null) + return createErrorMessage("No variable name specified"); + if (variable.contains(" ")) + return createErrorMessage("Variable name must not contain spaces: " + variable); + + if (!(memory instanceof AddressMemory)) + return createErrorMessage("Can't read mote memory variables (not address memory)"); + + if (!((AddressMemory) memory).variableExists(variable)) { + return createErrorMessage("Variable does not exist: " + variable); + } + + if (size == null) + return createErrorMessage("No size specified"); + int sizeParsed = Integer.parseInt(size); + if (sizeParsed < 0) { + return createErrorMessage("Bad size specified: " + sizeParsed); + } + + byte[] val = ((AddressMemory) memory).getByteArray(variable, sizeParsed); + String ret = ""; + for (byte b: val) + ret += (int) (0xff&b) + " "; + ret = ret.trim(); + return XML_OK_START + ret + XML_OK_END; + } else if (type.equals("address")) { + + if (size == null) + return createErrorMessage("No size specified"); + int sizeParsed = Integer.parseInt(size); + if (sizeParsed < 0) { + return createErrorMessage("Bad size specified: " + sizeParsed); + } + + int addressParsed = Integer.parseInt(address); + if (addressParsed < 0) { + return createErrorMessage("Bad start address specified: " + addressParsed); + } + + byte[] val = memory.getMemorySegment(addressParsed, sizeParsed); + String ret = ""; + for (byte b: val) + ret += (int) (0xff&b) + " "; + ret = ret.trim(); + return XML_OK_START + ret + XML_OK_END; + } + + return createErrorMessage("Bad read memory type specified: " + type); + } + + /** + * Write mote variable value as specified by arguments. + * + * @param arguments Command arguments + * @return Reply to client (XML format) + */ + private String writeMemory(Collection arguments) { + Simulation simulation = myGUI.getSimulation(); + + String type = null; + String mote = null; + String variable = null; + String size = null; + String address = null; + String value = null; + + for (Element element : arguments) { + if (element.getName().equals(XML_TYPE_NAME)) { + type = element.getText(); + } else if (element.getName().equals(XML_MOTE_NAME)) { + mote = element.getText(); + } else if (element.getName().equals(XML_VARIABLE_NAME)) { + variable = element.getText(); + } else if (element.getName().equals(XML_SIZE_NAME)) { + size = element.getText(); + } else if (element.getName().equals(XML_ADDRESS_NAME)) { + address = element.getText(); + } else if (element.getName().equals(XML_VALUE_NAME)) { + value = element.getText(); + } else { + return createErrorMessage("Unknown write memory parameter: " + element.getName()); + } + } + + if (type == null) + return createErrorMessage("No write memory type specified"); + if (mote == null) + return createErrorMessage("No mote specified"); + + if (mote == null) + return createErrorMessage("No mote ID specified"); + int moteNr = Integer.parseInt(mote); + if (moteNr < 0 || simulation.getMotesCount() <= moteNr) { + return createErrorMessage("Bad mote ID specified: " + moteNr); + } + MoteMemory memory = simulation.getMote(moteNr).getMemory(); + + // Write integer variable + if (type.equals("int")) { + if (variable == null) + return createErrorMessage("No variable name specified"); + if (variable.contains(" ")) + return createErrorMessage("Variable name must not contain spaces: " + variable); + + if (!(memory instanceof AddressMemory)) + return createErrorMessage("Can't read mote memory variables (not address memory)"); + + if (!((AddressMemory) memory).variableExists(variable)) { + return createErrorMessage("Variable does not exist: " + variable); + } + + if (value == null) + return createErrorMessage("No value specified"); + if (value.contains(" ")) + return createErrorMessage("Integer value must not contain spaces: " + value); + + int val; + try { + val = Integer.parseInt(value); + } catch (NumberFormatException e) { + return createErrorMessage("Bad integer value specified: " + e); + } + + ((AddressMemory) memory).setIntValueOf(variable, val); + return XML_OK; + } else if (type.equals("variable")) { + if (variable == null) + return createErrorMessage("No variable name specified"); + if (variable.contains(" ")) + return createErrorMessage("Variable name must not contain spaces: " + variable); + + if (!(memory instanceof AddressMemory)) + return createErrorMessage("Can't read mote memory variables (not address memory)"); + + if (!((AddressMemory) memory).variableExists(variable)) { + return createErrorMessage("Variable does not exist: " + variable); + } + + if (size == null) + return createErrorMessage("No size specified"); + int sizeParsed = Integer.parseInt(size); + if (sizeParsed < 0) { + return createErrorMessage("Bad size specified: " + sizeParsed); + } + + if (value == null) + return createErrorMessage("No value specified"); + String[] bytesParsed = value.split(" "); + if (bytesParsed.length != sizeParsed) + return createErrorMessage("Number of bytes and specified size does not match: " + bytesParsed.length + "!=" + sizeParsed); + + byte[] val = new byte[bytesParsed.length]; + for (int i=0; i < sizeParsed; i++) { + val[i] = Byte.parseByte(bytesParsed[i]); + } + + ((AddressMemory) memory).setByteArray(variable, val); + return XML_OK; + } else if (type.equals("address")) { + + if (size == null) + return createErrorMessage("No size specified"); + int sizeParsed = Integer.parseInt(size); + if (sizeParsed < 0) { + return createErrorMessage("Bad size specified: " + sizeParsed); + } + + int addressParsed = Integer.parseInt(address); + if (addressParsed < 0) { + return createErrorMessage("Bad start address specified: " + addressParsed); + } + + if (value == null) + return createErrorMessage("No value specified"); + String[] bytesParsed = value.split(" "); + if (bytesParsed.length != sizeParsed) + return createErrorMessage("Number of bytes and specified size does not match: " + bytesParsed.length + "!=" + sizeParsed); + + byte[] val = new byte[bytesParsed.length]; + for (int i=0; i < sizeParsed; i++) { + val[i] = Byte.parseByte(bytesParsed[i]); + } + + memory.setMemorySegment(addressParsed, val); + return XML_OK; + } + + return createErrorMessage("Bad write memory type specified: " + type); + } + + /** + * Handle add new eventpoint command. + * + * @param arguments Command arguments + * @return Reply to client (XML format) + */ + private String addEventpoint(Collection arguments) { + Simulation simulation = myGUI.getSimulation(); + + String type = null; + String mote = null; + String variable = null; + String time = null; + String size = null; + String triggeron = null; + String address = null; + String count = null; + + for (Element element : arguments) { + if (element.getName().equals(XML_TYPE_NAME)) { + type = element.getText(); + } else if (element.getName().equals(XML_MOTE_NAME)) { + mote = element.getText(); + } else if (element.getName().equals(XML_VARIABLE_NAME)) { + variable = element.getText(); + } else if (element.getName().equals(XML_TIME_NAME)) { + time = element.getText(); + } else if (element.getName().equals(XML_SIZE_NAME)) { + size = element.getText(); + } else if (element.getName().equals(XML_ADDRESS_NAME)) { + address = element.getText(); + } else if (element.getName().equals(XML_TRIGGERON_NAME)) { + triggeron = element.getText(); + } else if (element.getName().equals("count")) { + count = element.getText(); + } else { + return createErrorMessage("Unknown eventpoint parameter: " + element.getName()); + } + } + + logger.debug("Eventpoint type: " + type); + if (type == null) + return createErrorMessage("No eventpoint type specified"); + + // Integer variable watchpoint + if (type.equals(XML_WATCHPOINT_INT)) { + if (variable == null) + return createErrorMessage("No variable name specified"); + if (variable.contains(" ")) + return createErrorMessage("Variable name must not contain spaces: " + variable); + if (mote == null) + return createErrorMessage("No mote ID specified"); + int moteNr = Integer.parseInt(mote); + if (moteNr < 0 || simulation.getMotesCount() <= moteNr) { + return createErrorMessage("Bad mote ID specified: " + moteNr); + } + Mote moteObject = simulation.getMote(moteNr); + + MoteMemory memory = simulation.getMote(moteNr).getMemory(); + + if (!(memory instanceof AddressMemory)) + return createErrorMessage("Can't write mote memory variables (not address memory)"); + + if (!((AddressMemory) memory).variableExists(variable)) { + return createErrorMessage("Variable does not exist: " + variable); + } + + Eventpoint newEventpoint = new IntegerWatchpoint(moteObject, variable); + myEvaluator.addEventpoint(newEventpoint); + return createOkMessage(newEventpoint, simulation.getSimulationTime()); + } + + // Variable watchpoint + if (type.equals(XML_WATCHPOINT_VARIABLE)) { + if (variable == null) + return createErrorMessage("No variable name specified"); + if (variable.contains(" ")) + return createErrorMessage("Variable name must not contain spaces: " + variable); + if (mote == null) + return createErrorMessage("No mote ID specified"); + if (size == null) + return createErrorMessage("No size specified"); + int sizeParsed = Integer.parseInt(size); + if (sizeParsed < 0) { + return createErrorMessage("Bad size specified: " + sizeParsed); + } + int moteNr = Integer.parseInt(mote); + if (moteNr < 0 || simulation.getMotesCount() <= moteNr) { + return createErrorMessage("Bad mote ID specified: " + moteNr); + } + Mote moteObject = simulation.getMote(moteNr); + + MoteMemory memory = simulation.getMote(moteNr).getMemory(); + + if (!(memory instanceof AddressMemory)) + return createErrorMessage("Can't write mote memory variables (not address memory)"); + + if (!((AddressMemory) memory).variableExists(variable)) { + return createErrorMessage("Variable does not exist: " + variable); + } + + Eventpoint newEventpoint = new VariableWatchpoint(moteObject, variable, sizeParsed); + myEvaluator.addEventpoint(newEventpoint); + return createOkMessage(newEventpoint, simulation.getSimulationTime()); + } + + + // Memory area watchpoint + if (type.equals(XML_WATCHPOINT_ADDRESS)) { + if (mote == null) + return createErrorMessage("No mote ID specified"); + if (size == null) + return createErrorMessage("No size specified"); + int sizeParsed = Integer.parseInt(size); + if (sizeParsed < 0) { + return createErrorMessage("Bad size specified: " + sizeParsed); + } + int addressParsed = Integer.parseInt(address); + if (addressParsed < 0) { + return createErrorMessage("Bad start address specified: " + addressParsed); + } + int moteNr = Integer.parseInt(mote); + if (moteNr < 0 || simulation.getMotesCount() <= moteNr) { + return createErrorMessage("Bad mote ID specified: " + moteNr); + } + Mote moteObject = simulation.getMote(moteNr); + MoteMemory memory = simulation.getMote(moteNr).getMemory(); + + Eventpoint newEventpoint = new Watchpoint(moteObject, addressParsed, sizeParsed); + myEvaluator.addEventpoint(newEventpoint); + return createOkMessage(newEventpoint, simulation.getSimulationTime()); + } + + // Simulation timepoint + if (type.equals(XML_TIMEPOINT_SIMULATION)) { + if (time == null) + return createErrorMessage("No time specified"); + int timeParsed = Integer.parseInt(time); + if (timeParsed < 0) { + return createErrorMessage("Bad time specified: " + timeParsed); + } + + Eventpoint newEventpoint = new SimulationTimepoint(simulation, timeParsed); + myEvaluator.addEventpoint(newEventpoint); + return createOkMessage(newEventpoint, simulation.getSimulationTime()); + } + + // Real timepoint + if (type.equals(XML_TIMEPOINT_REAL)) { + if (time == null) + return createErrorMessage("No time specified"); + long timeParsed = Long.parseLong(time); + if (timeParsed < 0) { + return createErrorMessage("Bad time specified: " + timeParsed); + } + + Eventpoint newEventpoint = new RealTimepoint(timeParsed); + myEvaluator.addEventpoint(newEventpoint); + return createOkMessage(newEventpoint, simulation.getSimulationTime()); + } + + // Radio medium event point + if (type.equals(XML_EVENTPOINT_RADIOMEDIUM)) { + int countInt; + try { + countInt = Integer.parseInt(count); + } catch (NumberFormatException e) { + return createErrorMessage("Bad count specified: " + e); + } + + if (triggeron == null || triggeron.equals("all")) { + Eventpoint newEventpoint = new RadioMediumEventpoint(simulation.getRadioMedium(), countInt); + myEvaluator.addEventpoint(newEventpoint); + return createOkMessage(newEventpoint, simulation.getSimulationTime()); + } else if (triggeron.equals("completed")) { + Eventpoint newEventpoint = new TransmissionRadioMediumEventpoint(simulation.getRadioMedium(), countInt); + myEvaluator.addEventpoint(newEventpoint); + return createOkMessage(newEventpoint, simulation.getSimulationTime()); + } else { + return createErrorMessage("Bad trigger on parameter: " + triggeron); + } + } + + return createErrorMessage("Bad eventpoint type specified: " + type); + } + + /** + * Handle configure GUI command. + * + * @param arguments Command arguments + * @return Reply to client (XML format) + */ + private String configureGUI(Collection arguments) { + String visible = null; + + for (Element element : arguments) { + if (element.getName().equals(XML_VISIBLE_NAME)) { + visible = element.getText(); + } else { + return createErrorMessage("Unknown GUI configure parameter: " + element.getName()); + } + } + + boolean shouldBeVisible = Boolean.parseBoolean(visible); + if (myGUI.isVisualized() != shouldBeVisible) { + myGUI.setVisualized(shouldBeVisible); + } + return XML_OK; + } + + /** + * Handle delete eventpoint command. + * + * @param arguments Command arguments + * @return Reply to client (XML format) + */ + private String deleteEventpoint(Collection arguments) { + String id = null; + + for (Element element : arguments) { + if (element.getName().equals(XML_ID_NAME)) { + id = element.getText(); + } else { + return createErrorMessage("Unknown eventpoint parameter: " + element.getName()); + } + } + + int idInt; + try { + idInt = Integer.parseInt(id); + } catch (NumberFormatException e) { + return createErrorMessage("Bad eventpoint ID specified: " + e); + } + + Eventpoint eventpoint = myEvaluator.getEventpoint(idInt); + if (eventpoint == null) + return createErrorMessage("No eventpoint with ID " + idInt + " exists"); + + myEvaluator.deleteEventpoint(idInt); + return XML_OK; + } + + /** + * Handle kill nodes command. + * + * @param arguments Command arguments + * @return Reply to client (XML format) + */ + private String killNodes(Collection arguments) { + String lowId = null; + String highId = null; + + for (Element element : arguments) { + if (element.getName().equals("lowmote")) { + lowId = element.getText(); + } else if (element.getName().equals("highmote")) { + highId = element.getText(); + } else { + return createErrorMessage("Unknown eventpoint parameter: " + element.getName()); + } + } + + int lowIdInt; + int highIdInt; + try { + lowIdInt = Integer.parseInt(lowId); + highIdInt = Integer.parseInt(highId); + } catch (NumberFormatException e) { + return createErrorMessage("Bad mote interval specified: " + e); + } + + if (lowIdInt > highIdInt) + return createErrorMessage("Bad mote interval specified: Low ID must be <= than high ID"); + + if (lowIdInt < 0) + return createErrorMessage("Bad mote interval specified: Low ID >= 0"); + + if (myGUI.getSimulation() == null) + return createErrorMessage("No simulation exists"); + + if (myGUI.getSimulation().getMotesCount() < highIdInt) + return createErrorMessage("Bad mote interval specified: Only " + myGUI.getSimulation().getMotesCount() + " motes exist"); + + for (int pos=lowIdInt; pos <= highIdInt; pos++) { + Mote mote = myGUI.getSimulation().getMote(pos); + mote.setState(State.DEAD); + } + return XML_OK; + } + + /** + * Handle get info command. + * + * @param arguments Command arguments + * @return Reply to client (XML format) + */ + private String getInfo(Collection arguments) { + Simulation simulation = myGUI.getSimulation(); + String type = null; + String mote = null; + String variable = null; + + for (Element element : arguments) { + if (element.getName().equals(XML_TYPE_NAME)) { + type = element.getText(); + } else if (element.getName().equals(XML_MOTE_NAME)) { + mote = element.getText(); + } else if (element.getName().equals(XML_VARIABLE_NAME)) { + variable = element.getText(); + } else { + return createErrorMessage("Unknown info parameter: " + element.getName()); + } + } + + if (type.equals("motestate")) { + if (mote == null) + return createErrorMessage("No mote ID specified"); + int moteNr = Integer.parseInt(mote); + if (moteNr < 0 || simulation.getMotesCount() <= moteNr) { + return createErrorMessage("Bad mote ID specified: " + moteNr); + } + + State state = simulation.getMote(moteNr).getState(); + if (state == State.DEAD) + return XML_OK_START + 0 + XML_OK_END; + return XML_OK_START + 1 + XML_OK_END; + + } else if (type.equals("time")) { + return XML_OK_START + myGUI.getSimulation().getSimulationTime() + XML_OK_END; + } else if (type.equals(XML_MOTECOUNT_NAME)) { + return XML_OK_START + myGUI.getSimulation().getMotesCount() + XML_OK_END; + } else if (type.equals("var_address")) { + if (variable == null) + return createErrorMessage("No variable name specified"); + if (variable.contains(" ")) + return createErrorMessage("Variable name must not contain spaces: " + variable); + if (mote == null) + return createErrorMessage("No mote ID specified"); + int moteNr = Integer.parseInt(mote); + if (moteNr < 0 || simulation.getMotesCount() <= moteNr) { + return createErrorMessage("Bad mote ID specified: " + moteNr); + } + Mote moteObject = simulation.getMote(moteNr); + MoteMemory memory = simulation.getMote(moteNr).getMemory(); + + if (!(memory instanceof AddressMemory)) + return createErrorMessage("Can't read mote memory variable address (not address memory)"); + + if (!((AddressMemory) memory).variableExists(variable)) { + return createErrorMessage("Variable does not exist: " + variable); + } + + int address = ((AddressMemory) memory).getVariableAddress(variable); + return XML_OK_START + address + XML_OK_END; + } + + return createErrorMessage("Unknown type: " + type); + } + + + /** + * Handle custom command. + * + * @param arguments Command arguments + * @return Reply to client (XML format) + */ + private String handleCustomCommand(Collection arguments) { + Simulation simulation = myGUI.getSimulation(); + String action = null; + int id = -1; + int visible = -1; + int hide = -1; + + for (Element element : arguments) { + if (element.getName().equals("action")) { + action = element.getText(); + } else if (element.getName().equals("id")) { + String idString = element.getText(); + id = Integer.parseInt(idString); + } else if (element.getName().equals("hide")) { + String hideString = element.getText(); + hide = Integer.parseInt(hideString); + } else if (element.getName().equals("visible")) { + String visibleString = element.getText(); + visible = Integer.parseInt(visibleString); + } else { + return createErrorMessage("Unknown info parameter: " + element.getName()); + } + } + + if (action.equals("CLICK_SINK")) { + simulation.getMote(0).getInterfaces().getButton().clickButton(); + return XML_OK; + } + + if (action.equals("SHOW_FIRE")) { + if (!simulation.getGUI().isVisualized()) + return XML_OK; + + JInternalFrame[] allPlugins = simulation.getGUI().getDesktopPane().getAllFrames(); + + try { + Class pluginClass = (Class) simulation.getGUI().tryLoadClass(this, Visualizer2D.class, "se.sics.runes.RunesVis"); + Class[] parameterTypes = new Class[1]; + parameterTypes[0] = Boolean.TYPE; + Method method = pluginClass.getMethod("showFire", parameterTypes); + Object[] parameterArguments = new Object[1]; + parameterArguments[0] = new Boolean(visible==1?true:false); + + for (JInternalFrame plugin: allPlugins) { + if (plugin.getClass() == pluginClass) { + method.invoke(plugin, parameterArguments); + } + } + + } catch (Exception e) { + return createErrorMessage("Exception: " + e.getMessage()); + } + return XML_OK; + } + + if (action.equals("RESET_COLORS")) { + if (!simulation.getGUI().isVisualized()) + return XML_OK; + + JInternalFrame[] allPlugins = simulation.getGUI().getDesktopPane().getAllFrames(); + + try { + Class pluginClass = (Class) simulation.getGUI().tryLoadClass(this, Visualizer2D.class, "se.sics.runes.RunesVis"); + Class[] parameterTypes = new Class[0]; + Method method = pluginClass.getMethod("resetColors", parameterTypes); + Object[] parameterArguments = new Object[0]; + + for (JInternalFrame plugin: allPlugins) { + if (plugin.getClass() == pluginClass) { + method.invoke(plugin, parameterArguments); + } + } + + } catch (Exception e) { + return createErrorMessage("Exception: " + e.getMessage()); + } + return XML_OK; + } + + if (action.equals("SHOW_TRUCK")) { + if (!simulation.getGUI().isVisualized()) + return XML_OK; + + JInternalFrame[] allPlugins = simulation.getGUI().getDesktopPane().getAllFrames(); + + try { + Class pluginClass = (Class) simulation.getGUI().tryLoadClass(this, Visualizer2D.class, "se.sics.runes.RunesVis"); + + if (hide == 0) { + Class[] parameterTypes = new Class[0]; + Method method = pluginClass.getMethod("showTruck", parameterTypes); + Object[] parameterArguments = new Object[0]; + + for (JInternalFrame plugin: allPlugins) { + if (plugin.getClass() == pluginClass) { + method.invoke(plugin, parameterArguments); + } + } + } else { + Class[] parameterTypes = new Class[0]; + Method method = pluginClass.getMethod("hideTruck", parameterTypes); + Object[] parameterArguments = new Object[0]; + + for (JInternalFrame plugin: allPlugins) { + if (plugin.getClass() == pluginClass) { + method.invoke(plugin, parameterArguments); + } + } + } + + } catch (Exception e) { + return createErrorMessage("Exception: " + e.getMessage()); + } + return XML_OK; + } + + if (action.equals("INDICATE_MOTE")) { + if (!simulation.getGUI().isVisualized()) + return XML_OK; + + JInternalFrame[] allPlugins = simulation.getGUI().getDesktopPane().getAllFrames(); + + try { + Class pluginClass = (Class) simulation.getGUI().tryLoadClass(this, Visualizer2D.class, "se.sics.runes.RunesVis"); + Class[] parameterTypes = new Class[1]; + parameterTypes[0] = Integer.TYPE; + Method method = pluginClass.getMethod("indicateMote", parameterTypes); + Object[] parameterArguments = new Object[1]; + parameterArguments[0] = new Integer(id); + + for (JInternalFrame plugin: allPlugins) { + if (plugin.getClass() == pluginClass) { + method.invoke(plugin, parameterArguments); + } + } + + } catch (Exception e) { + return createErrorMessage("Exception: " + e.getMessage()); + } + return XML_OK; + } + + + return createErrorMessage("Unknown action: " + action); + } + + /** + * Handle clear eventpoints command. + * + * @return Reply to client (XML format) + */ + private String clearEventpoints() { + myEvaluator.clearAllEventpoints(); + return XML_OK; + } + + public static String createErrorMessage(String message) { + return XML_ERROR_START + message + XML_ERROR_END; + } + + public static String createInfoMessage(String message) { + return XML_INFO_START + message + XML_INFO_END; + } + + public static String createOkMessage(Eventpoint eventpoint, int time) { + return XML_OK_START + eventpoint.getID() + XML_OK_END; + } + + public static String createEventpointMessage(EventpointEvaluator evaluator) { + return XML_EVENTPOINT_START + + evaluator.getLastTriggeredEventpointID() + + XML_INFO_START + + evaluator.getTriggeredEventpoint().getMessage() + + XML_INFO_END + + XML_EVENTPOINT_END; + } +} diff --git a/tools/chakana/cooja_plugin/java/se/sics/chakana/eventpoints/Eventpoint.java b/tools/chakana/cooja_plugin/java/se/sics/chakana/eventpoints/Eventpoint.java new file mode 100644 index 000000000..d990fe3aa --- /dev/null +++ b/tools/chakana/cooja_plugin/java/se/sics/chakana/eventpoints/Eventpoint.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2007, Swedish Institute of Computer Science. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of the + * Institute nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $Id: Eventpoint.java,v 1.1 2007/08/21 14:39:58 fros4943 Exp $ + */ + +package se.sics.chakana.eventpoints; + +public interface Eventpoint { + + /** + * Evaluates eventpoint. + * + * @return True if eventpoint triggered, false otherwise + */ + public boolean evaluate(); + + /** + * @return Optional information of triggered eventpoint + */ + public String getMessage(); + + /** + * @param id Eventpoint ID + */ + public void setID(int id); + + /** + * @return Unique eventpoint ID + */ + public int getID(); + +} diff --git a/tools/chakana/cooja_plugin/java/se/sics/chakana/eventpoints/IntegerWatchpoint.java b/tools/chakana/cooja_plugin/java/se/sics/chakana/eventpoints/IntegerWatchpoint.java new file mode 100644 index 000000000..12cd4956f --- /dev/null +++ b/tools/chakana/cooja_plugin/java/se/sics/chakana/eventpoints/IntegerWatchpoint.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2007, Swedish Institute of Computer Science. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of the + * Institute nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $Id: IntegerWatchpoint.java,v 1.1 2007/08/21 14:39:58 fros4943 Exp $ + */ + +package se.sics.chakana.eventpoints; + +import se.sics.cooja.Mote; + +/** + * TODO Document + * + * @author Fredrik Osterlind + */ +public class IntegerWatchpoint extends VariableWatchpoint { + public IntegerWatchpoint(Mote mote, String varName) { + super(mote, varName, 4); + } +} diff --git a/tools/chakana/cooja_plugin/java/se/sics/chakana/eventpoints/RadioMediumEventpoint.java b/tools/chakana/cooja_plugin/java/se/sics/chakana/eventpoints/RadioMediumEventpoint.java new file mode 100644 index 000000000..dc303b951 --- /dev/null +++ b/tools/chakana/cooja_plugin/java/se/sics/chakana/eventpoints/RadioMediumEventpoint.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2007, Swedish Institute of Computer Science. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of the + * Institute nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $Id: RadioMediumEventpoint.java,v 1.1 2007/08/21 14:39:58 fros4943 Exp $ + */ + +package se.sics.chakana.eventpoints; + +import java.util.Observable; +import java.util.Observer; + +import se.sics.cooja.RadioMedium; + +/** + * Triggers if radio medium changes + * + * @author Fredrik Osterlind + */ +public class RadioMediumEventpoint implements Eventpoint { + protected boolean shouldBreak = false; + private int myID = 0; + protected RadioMedium radioMedium; + private int count = 1; + + public RadioMediumEventpoint(RadioMedium radioMedium) { + if (radioMedium == null) { + // TODO Throw exception + return; + } + + this.radioMedium = radioMedium; + + // Register as radio medium observer + radioMedium.addRadioMediumObserver(new Observer() { + public void update(Observable obs, Object obj) { + handleRadioMediumChange(); + } + }); + } + + public RadioMediumEventpoint(RadioMedium radioMedium, int count) { + this(radioMedium); + this.count = count; + } + + protected void handleRadioMediumChange() { + count--; + if (count < 1) + shouldBreak = true; + } + + public String getMessage() { + return "Radio medium changed"; + } + + public boolean evaluate() { + return shouldBreak; + } + + public void setID(int id) { + myID = id; + } + + public int getID() { + return myID; + } +} diff --git a/tools/chakana/cooja_plugin/java/se/sics/chakana/eventpoints/RealTimepoint.java b/tools/chakana/cooja_plugin/java/se/sics/chakana/eventpoints/RealTimepoint.java new file mode 100644 index 000000000..54981ac02 --- /dev/null +++ b/tools/chakana/cooja_plugin/java/se/sics/chakana/eventpoints/RealTimepoint.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2007, Swedish Institute of Computer Science. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of the + * Institute nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $Id: RealTimepoint.java,v 1.1 2007/08/21 14:39:58 fros4943 Exp $ + */ + +package se.sics.chakana.eventpoints; + +/** + * TODO Document + * + * @author Fredrik Osterlind + */ +public class RealTimepoint implements Timepoint { + private int myID = 0; + private long endTime; + + private String message; + + /** + * TODO Document + * + * TODO Use timer instead, much faster! + * + * @param time Duration in milliseconds + */ + public RealTimepoint(long time) { + this.endTime = System.currentTimeMillis() + time; + } + + public boolean evaluate() { + if (System.currentTimeMillis() >= endTime) { + message = "Real time = " + System.currentTimeMillis() + " >= " + endTime; + return true; + } + return false; + } + + public String getMessage() { + return message; + } + + public String toString() { + return "Real timepoint: " + endTime; + } + + public void setID(int id) { + myID = id; + } + + public int getID() { + return myID; + } +} diff --git a/tools/chakana/cooja_plugin/java/se/sics/chakana/eventpoints/SimulationTimepoint.java b/tools/chakana/cooja_plugin/java/se/sics/chakana/eventpoints/SimulationTimepoint.java new file mode 100644 index 000000000..05865432f --- /dev/null +++ b/tools/chakana/cooja_plugin/java/se/sics/chakana/eventpoints/SimulationTimepoint.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2007, Swedish Institute of Computer Science. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of the + * Institute nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $Id: SimulationTimepoint.java,v 1.1 2007/08/21 14:39:58 fros4943 Exp $ + */ + +package se.sics.chakana.eventpoints; + +import se.sics.cooja.Simulation; + +/** + * TODO Document + * + * @author Fredrik Osterlind + */ +public class SimulationTimepoint implements Timepoint { + private int myID = 0; + private Simulation simulation; + private int time; + + private String message; + + /** + * TODO Document + * + * @param simulation + * @param time + */ + public SimulationTimepoint(Simulation simulation, int time) { + this.simulation = simulation; + this.time = time; + } + + public boolean evaluate() { + if (simulation.getSimulationTime() >= time) { + message = "Simulation time = " + simulation.getSimulationTime() + " >= " + time; + return true; + } + return false; + } + + public String getMessage() { + return message; + } + + public String toString() { + return "Simulation timepoint: " + time; + } + + public void setID(int id) { + myID = id; + } + + public int getID() { + return myID; + } +} diff --git a/tools/chakana/cooja_plugin/java/se/sics/chakana/eventpoints/Timepoint.java b/tools/chakana/cooja_plugin/java/se/sics/chakana/eventpoints/Timepoint.java new file mode 100644 index 000000000..a7be67fe7 --- /dev/null +++ b/tools/chakana/cooja_plugin/java/se/sics/chakana/eventpoints/Timepoint.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2007, Swedish Institute of Computer Science. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of the + * Institute nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $Id: Timepoint.java,v 1.1 2007/08/21 14:39:58 fros4943 Exp $ + */ + +package se.sics.chakana.eventpoints; + +/** + * A timepoint triggers at and after a given time. + * + * @author Fredrik Osterlind + */ +public interface Timepoint extends Eventpoint { +} diff --git a/tools/chakana/cooja_plugin/java/se/sics/chakana/eventpoints/TransmissionRadioMediumEventpoint.java b/tools/chakana/cooja_plugin/java/se/sics/chakana/eventpoints/TransmissionRadioMediumEventpoint.java new file mode 100644 index 000000000..b3b7b6195 --- /dev/null +++ b/tools/chakana/cooja_plugin/java/se/sics/chakana/eventpoints/TransmissionRadioMediumEventpoint.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2007, Swedish Institute of Computer Science. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of the + * Institute nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $Id: TransmissionRadioMediumEventpoint.java,v 1.1 2007/08/21 14:39:58 fros4943 Exp $ + */ + +package se.sics.chakana.eventpoints; + +import se.sics.cooja.RadioMedium; + +/** + * Triggers when a radio medium transmission has been completed + * + * @author Fredrik Osterlind + */ +public class TransmissionRadioMediumEventpoint extends RadioMediumEventpoint { + private String message = ""; + private int count = 1; + + public TransmissionRadioMediumEventpoint(RadioMedium radioMedium) { + super(radioMedium); + } + + public TransmissionRadioMediumEventpoint(RadioMedium radioMedium, int count) { + this(radioMedium); + this.count = count; + } + + protected void handleRadioMediumChange() { + if (radioMedium.getLastTickConnections() != null) { + count -= radioMedium.getLastTickConnections().length; + if (count < 1) { + shouldBreak = true; + message = radioMedium.getLastTickConnections().length + " transmissions were completed"; + } + } + } + + public String getMessage() { + return message; + } + +} diff --git a/tools/chakana/cooja_plugin/java/se/sics/chakana/eventpoints/VariableWatchpoint.java b/tools/chakana/cooja_plugin/java/se/sics/chakana/eventpoints/VariableWatchpoint.java new file mode 100644 index 000000000..da063760e --- /dev/null +++ b/tools/chakana/cooja_plugin/java/se/sics/chakana/eventpoints/VariableWatchpoint.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2007, Swedish Institute of Computer Science. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of the + * Institute nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $Id: VariableWatchpoint.java,v 1.1 2007/08/21 14:39:58 fros4943 Exp $ + */ + +package se.sics.chakana.eventpoints; + +import se.sics.cooja.Mote; +import se.sics.cooja.SectionMoteMemory; + +/** + * TODO Document + * + * @author Fredrik Osterlind + */ +public class VariableWatchpoint extends Watchpoint { + private Mote mote; + private String name; + + private String reason; + + public VariableWatchpoint(Mote mote, String variableName, int size) { + super(mote, ((SectionMoteMemory) mote.getMemory()) + .getVariableAddress(variableName), size); + this.mote = mote; + this.name = variableName; + } + + public boolean evaluate() { + boolean shouldBreak = super.evaluate(); + + if (shouldBreak) { + reason = "Variable '" + name + "' changed"; + } + return shouldBreak; + } + + public String getMessage() { + return reason; + } + + public String toString() { + return "Variable watchpoint: " + name + " @ " + mote; + } +} diff --git a/tools/chakana/cooja_plugin/java/se/sics/chakana/eventpoints/Watchpoint.java b/tools/chakana/cooja_plugin/java/se/sics/chakana/eventpoints/Watchpoint.java new file mode 100644 index 000000000..b357c2b2b --- /dev/null +++ b/tools/chakana/cooja_plugin/java/se/sics/chakana/eventpoints/Watchpoint.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2007, Swedish Institute of Computer Science. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of the + * Institute nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $Id: Watchpoint.java,v 1.1 2007/08/21 14:39:58 fros4943 Exp $ + */ + +package se.sics.chakana.eventpoints; + +import java.util.Arrays; +import org.apache.log4j.Logger; + +import se.sics.cooja.Mote; + +/** + * A watchpoint watches a memory area, such as a variable, and triggers at changes. + * + * @author Fredrik Osterlind + */ +public class Watchpoint implements Eventpoint { + private static Logger logger = Logger.getLogger(Watchpoint.class); + private int myID = 0; + + private Mote mote; + private int address; + private int size; + byte[] initialMemory; + + private String reason; + + public Watchpoint(Mote mote, int address, int size) { + this.mote = mote; + this.address = address; + this.size = size; + + logger.debug("Fetching initial memory"); + initialMemory = mote.getMemory().getMemorySegment(address, size); + + // TODO Throw exception if memory area not valid + } + + public boolean evaluate() { + byte[] currentMemory = mote.getMemory().getMemorySegment(address, size); + boolean shouldBreak = !Arrays.equals(initialMemory, currentMemory); + + if (shouldBreak) { + reason = "Memory interval " + "0x" + Integer.toHexString(address) + ":" + size + " changed"; + } + return shouldBreak; + } + + public String getMessage() { + return reason; + } + + public String toString() { + return "Memory change breakpoint: " + "0x" + Integer.toHexString(address) + ":" + size + " @ " + mote; + } + + public void setID(int id) { + myID = id; + } + + public int getID() { + return myID; + } +} diff --git a/tools/chakana/debug.py b/tools/chakana/debug.py new file mode 100644 index 000000000..9fcdd0588 --- /dev/null +++ b/tools/chakana/debug.py @@ -0,0 +1,279 @@ +# +# Copyright (C) 2003-2007 Swedish Institute of Computer Science. +# +# Please refer to the file named LICENSE in the same directory as this +# file for licensing information. +# + +# $Id: debug.py,v 1.1 2007/08/21 14:41:01 fros4943 Exp $ + +import inspect +import os +import sys +import threading +import StringIO + +DebugMin = 0 +Always = 0 +Fatal = 1 +Critical = 2 +Error = 3 +Warning = 4 +Check = 5 +Information = 6 +MajorEvent = 7 +Event = 8 +MinorEvent = 9 +Debug = 10 +Debug2 = 11 +Debug3 = 12 +Debug4 = 13 +Disabled = 14 +DebugMax = 15 + +DefaultDebugLevel = Information + +def levelName(level): + for (varName, value) in globals().items(): + if not varName in ("DebugMin", "DebugMax"): + if value == level: + return varName + return "Unknown" + +class DebugSingleton: + def __init__(self, output = sys.stderr, defaultLevel = DefaultDebugLevel, + logLevel = Debug4): + self.maxBufferLen = 1024 * 1024 + self.__level = defaultLevel + self.__output = output + self.__logBuffer = [] + self.__logBufferSize = 0 + self.__logLevel = Debug4 + self.__logFile = None + self.__logFileName = None + self.__lock = threading.RLock() + self._transformers = [] + + def level(self): + return self.__level + + def setLog(self, fileName): + assert(not self.__logBuffer is None) + if not fileName is None: + self.__logFileName = fileName + self.__logFile = open(fileName, "w") + self.__logFile.write("".join(self.__logBuffer)) + self.write(MajorEvent, "Saving debug log in " + fileName + "\n") + self.__logBuffer = None + + def logFileName(self): + return self.__logFileName + + def logLevel(self): + return self.__logLevel + + def cleanLog(self): + self.setLog(None) + + def setLogLevel(self, level): + self.__logLevel = level + self.write(MinorEvent, "Changed debug log verbosity to " + + levelName(self.__logLevel) + " (" + + str(self.__logLevel) + ")\n") + + def setLevel(self, level): + self.__level = level + if self.__level < Always: + self.__level = Always + if self.__level >= Disabled: + self.__level = Disabled - 1 + self.write(MinorEvent, "Changed debug verbosity to " + + levelName(self.__level) + " (" + str(self.__level) + ")\n") + + def increaseLevel(self, amount = 1): + self.setLevel(self.level() + amount) + + def decreaseLevel(self, amount = 1): + self.increaseLevel(- amount) + + def write(self, level, * messages): + """Write messages if current verbosity level >= level. Messages should + either be callable functors taking a writer function parameter, or they + should be strings or convertible to strings.""" + + writers = [] + if self.level() >= level: + writers.append(self.__output.write) + if self.logLevel() >= level: + writers.append(self.logWrite) + if writers != []: + self.__lock.acquire() + try: + for writer in writers: + writer(self.transform(level = level, + message = self.format(messages))) + finally: + self.__lock.release() + + def format(self, messages): + buf = StringIO.StringIO() + for msg in messages: + if callable(msg): + msg(buf.write) + else: + buf.write(msg) + return buf.getvalue() + + def transform(self, level, message): + for transform in self._transformers: + message = transform(level = level, message = message) + return message + + def writer(self, level): + return lambda what: self.write(level, what) + + def logWrite(self, what): + if self.__logFile is None: + if not self.__logBuffer is None: + self.__logBuffer.append(what) + self.__logBufferSize += len(what) + if self.__logBufferSize > self.maxBufferLen: + self.__logBuffer = None + self.write(Warning, "Cleaned log buffer since it overflowed " + + str(self.maxBufferLen) + " bytes\n") + else: + self.__logFile.write(what) + self.__logFile.flush() + + def pushTransformer(self, transformer): + assert(callable(transformer)) + if not transformer in self._transformers: + self._transformers.append(transformer) + +debugStream = DebugSingleton() + +def debug(level, message): + debugStream.write(level, message, "\n") + + +class DebugDumper: + def __init__(self): + pass + + def __call__(self, writer): + raise NotImplementedError() + + def __add__(self, other): + return DebugCombiner(self, other) + + def __radd__(self, other): + return DebugCombiner(other, self) + + +class DebugCombiner: + def __init__(self, first, second): + self.__first = first + self.__second = second + + def __call__(self, writer): + for elem in [self.__first, self.__second]: + if callable(elem): + elem(writer) + else: + writer(str(elem)) + +class DebugFile: + def __init__(self): + self.__str = "" + + def write(self, msg): + self.__str += msg + + def __str__(self): + return self.__str + +DebugStderr = DebugFile + +def exceptionDump((type, value, traceback)): + """Print an exception stack trace to DebugStderr(). Pass + sys.exc_info() as parameter.""" + if type is None: + return "" + oldStderr = sys.stderr + debugStderr = DebugStderr() + sys.stderr = debugStderr + sys.__excepthook__(type, value, traceback) + sys.stderr = oldStderr + return str(debugStderr) + +def exceptionMessage((type, value, traceback)): + import urllib2 + if isinstance(value, urllib2.HTTPError): + return str(value) + ": " + value.filename + else: + return str(value) + +class CallTracer: + def __init__(self, level, prefix = lambda f, e, a: "", traceSystem = 0, + traceDebug = 0): + self._level = level + self._prefix = prefix + self._traceSystem = traceSystem + self._traceDebug = traceDebug + + def indent(self, frame): + stack = inspect.stack() + depth = len(stack) - 4 + assert(depth >= 0) + while len(stack) > 0: + del stack[0] + del stack + return " " * depth + + def prefix(self, frame, event, arg): + return self._prefix(frame, event, arg) + self.indent(frame) + + def inDebugCode(self, frame): + if frame is None: + return 0 + if os.path.basename(frame.f_code.co_filename) == "debug.py": + ret = 1 + else: + ret = self.inDebugCode(frame.f_back) + del frame + return ret + + def __call__(self, frame, event, arg): + if frame.f_back is None: + callerDir = "" + else: + callerDir = os.path.dirname(frame.f_back.f_code.co_filename) + sourceFile = os.path.basename(frame.f_code.co_filename) + funcName = sourceFile + ":" + frame.f_code.co_name + "()" + if (not self._traceDebug) and self.inDebugCode(frame): + return None + if self._traceSystem or \ + not (callerDir.startswith(sys.prefix + "/lib/python")): + if event == "call": + debug(self._level, self.prefix( + frame = frame, event = event, arg = arg) + "-> " + funcName) + elif event == "return": + debug(self._level, self.prefix( + frame = frame, event = event, arg = arg) + "<- " + funcName) + elif event == "exception": + debug(self._level, self.prefix( + frame = frame, event = event, arg = arg) + "X " + funcName) + return self + +def pidPrefix(message = "", ** kwArgs): + return str(os.getpid()) + ": " + message + +def threadPrefix(message = "", ** kwArgs): + return threading.currentThread().getName() + ": " + message + +def objectAsString(obj): + names = [(name, value) for name,value in vars(obj).iteritems()] + names.insert(0,("Instace of", obj.__class__.__name__)) + max_length = len(max([name for (name, value) in names])) + return "\n".join([name.rjust(max_length) + ": " + str(value) for (name,value) in names]) + diff --git a/tools/chakana/error.py b/tools/chakana/error.py new file mode 100644 index 000000000..03c7ced17 --- /dev/null +++ b/tools/chakana/error.py @@ -0,0 +1,89 @@ +# +# Copyright (C) 2003-2007 Swedish Institute of Computer Science. +# +# Please refer to the file named LICENSE in the same directory as this +# file for licensing information. +# + +# $Id: error.py,v 1.1 2007/08/21 14:41:01 fros4943 Exp $ + +import errno +import os + +import chakana.debug + +class Error(Exception): + def __init__(self, message): + Exception.__init__(self, message) + +class CommandFailed(Error): + """Exception thrown when a system command fails. The command string, + environment, working directory, output, and exit status are recorded. + The exit status is typically the value returned by os.system, but may + also be an exception, for example an error.Timeout object.""" + + def __init__(self, command, status, output): + Error.__init__(self, "Command failed with exit code " + str(status) + + ": " + command + "\n" + "Output:\n" + output + "\ncwd: " + + os.getcwd() + "\n") + self._command = command + self._status = status + self._output = output + self._environ = os.environ.copy() + self._cwd = os.getcwd() + + def command(self): + return self._command + + def status(self): + return self._status + + def output(self): + return self._output + + def environ(self): + return self._environ + + def cwd(self): + return self._cwd + +class SanityCheckFailed(Error): + def __init__(self, message): + Error.__init__(self, "Sanity check failed: " + message) + +class NoSuchProcess(OSError): + def __init__(self): + OSError.__init__(self, errno.ESRCH, "No such process") + +class Timeout(Error): + def __init__(self, child, timeout): + Error.__init__(self, "Timeout (" + str(timeout) + " s) executing " + + child.getName()) + self.child = child + self.timeout = timeout + +class Discarded(Error): + def __init__(self): + Error.__init__(self, "Monitor was discarded") + +class ChildException(Error): + def __init__(self, error, exc_info): + Error.__init__(self, "Exception in child thread: " + str(error) + + ":\n" + chakana.debug.exceptionDump(exc_info)) + self.error = error + self.exc_info = exc_info + +class CoojaError(Error): + def __init__(self, response): + Error.__init__(self, "COOJA error: " + + response.documentElement.childNodes[0].nodeValue) + +class CoojaExit(Error): + def __init__(self): + Error.__init__(self, "COOJA has exited") + +def errnoError(errNum, fileName = None): + if isinstance(errNum, str): + return errnoError(getattr(errno, errNum), fileName) + return OSError(errNum, os.strerror(errNum), fileName) + diff --git a/tools/chakana/event.py b/tools/chakana/event.py new file mode 100644 index 000000000..8e0c92631 --- /dev/null +++ b/tools/chakana/event.py @@ -0,0 +1,175 @@ +import time +import thread + +from debug import * +import threads + +class Eventpoint(threads.ManagedThread): + + """ The Eventpoint class and its subclasses represent important events + happening in the debugged system, and are the primitives that Chakana + debugging scripts are based around. When the user calls waitFor with an + Eventpoint list argument, each Eventpoint thread is started. The + Eventpoint inserts appropriate eventpoints into COOJA or creates child + Eventpoints. An Eventpoint that is hit posts itself on a trigger queue + (Queue.Queue), where it is picked up by waitFor. The Eventpoints that + have not been triggered are then discarded by the waitFor routine.""" + + def __init__(self, shepherd): + threads.ManagedThread.__init__(self, shepherd.threadManager()) + self._shepherd = shepherd + self._activated = 0 + self._discarded = 0 + self._triggerQueue = None + + resourceAllocated = 0 + while resourceAllocated == 0: + try: + self._isWaitingEvent = threading.Event() + resourceAllocated = 1 + except thread.error: + resourceAllocated = 0 + shepherd.threadManager().registerPIDerror() + + def await(self, triggerQueue): + assert not self._activated, "Eventpoints can only be used once" + self._triggerQueue = triggerQueue + self._activated = 1 + self.activatedHook() + self.start() + + def doRun(self): + debug(Debug, "Waiting for event: " + repr(self)) + if not self._discarded: + self.doWait() + debug(MinorEvent, "Event was triggered: " + repr(self)) + if not self._discarded: + self._triggerQueue.put(self) + else: + debug(Debug, "Event is no longer waited for: " + repr(self)) + + def activatedHook(self): + pass + + def doWait(self): + raise NotImplementedError() + + def discard(self): + debug(Debug, "Discarding eventpoint:\n" + repr(self)) + self._discarded = 1 + if (not self._activated): + self.start() + for (waitingEv, event) in self.shepherd()._waitMap.values(): + if waitingEv == self: + event.set() + self.join() + + def isDiscarded(self): + return self._discarded + + def shepherd(self): + return self._shepherd + + def __repr__(self): + return "\n ".join([ + "event.Eventpoint:", + "Id: %#x" % ((id(self) + (long(1) << 32)) % (long(1) << 32)), + ]) + +class BasicEventpoint(Eventpoint): + """Eventpoint corresponding to a single COOJA Eventpoint.""" + + def __init__(self, shepherd, ** kwArgs): + Eventpoint.__init__(self, shepherd, ** kwArgs) + + def activatedHook(self): + self.shepherd().registerBasicEventpoint(self) + + def doWait(self): + self.shepherd().waitForCoojaEventpoint(self) + self.shepherd().unregisterBasicEventpoint(self) + +class Watchpoint(BasicEventpoint): + def __init__(self, shepherd, type, mote, variable, ** kwArgs): + self._type = type + self._mote = mote + self._variable = variable + BasicEventpoint.__init__(self, shepherd, ** kwArgs) + + def coojaArguments(self): + return { "type" : self._type, + "mote" : self._mote, + "variable" : self._variable } + + def __repr__(self): + return "\n ".join([ + BasicEventpoint.__repr__(self), + "event.Watchpoint:", + "Type: " + repr(self._type), + "Mote: " + repr(self._mote), + "Variable: " + repr(self._variable) + ]) + +class TimeEventpoint(BasicEventpoint): + def __init__(self, shepherd, time, ** kwArgs): + self._time = time + BasicEventpoint.__init__(self, shepherd, ** kwArgs) + + def coojaArguments(self): + return { "type" : "time", + "time" : self._time } + + def __repr__(self): + return "\n ".join([ + BasicEventpoint.__repr__(self), + "event.TimeEventpoint:", + "Time: " + repr(self._time) + ]) + + +class RadioMessageCompletedEventpoint(BasicEventpoint): + def __init__(self, shepherd, count = 1, ** kwArgs): + self._count = count + BasicEventpoint.__init__(self, shepherd, ** kwArgs) + + def coojaArguments(self): + return { "type" : "radiomedium", + "triggeron" : "completed", + "count" : str(self._count) } + + def __repr__(self): + return "\n ".join([ + BasicEventpoint.__repr__(self), + "event.RadioMessageCompletedEventpoint", + ]) + +class MonitorEventpoint(Eventpoint): + """Eventpoint that triggers after a monitor has finished.""" + + def __init__(self, monitor): + self._monitor = monitor + Eventpoint.__init__(self, monitor.shepherd()) + + def doWait(self): + debug(Debug, "MonitorEventpoint starting " + repr(self)) + self._monitor.startMonitor() + self._monitor.waitMonitor(self) + + def discard(self): + self._discarded = 1 + self._monitor.discard() + if (not self._activated): + self.start() + self._monitor.startMonitor() + self._monitor.waitMonitor(self) + else: + self._monitor.waitMonitor(self) + self.join() + + def __repr__(self): + return "\n ".join([ + Eventpoint.__repr__(self), + "event.MonitorEventpoint:", + "Monitor: " + repr(self._monitor) + ]) + diff --git a/tools/chakana/harness.py b/tools/chakana/harness.py new file mode 100644 index 000000000..ec06ffb45 --- /dev/null +++ b/tools/chakana/harness.py @@ -0,0 +1,126 @@ +import errno +import os +import re +import time + +import chakana.command +import chakana.error +import chakana.shepherd +import chakana.threads +import chakana.utils + +from chakana.debug import * + +class Harness: + def __init__(self, chakanaRoot, coojaTimeout, doCompile="True", withGUI="False"): + self._chakanaRoot = os.path.abspath(chakanaRoot) + self._coojaTimeout = coojaTimeout + self._threadManager = chakana.threads.ThreadManager() + self._doCompile = doCompile + self._withGUI = withGUI + debug(MajorEvent, "Chakana harness created") + + def start(self): + port = self.startCooja() + self._shepherd = chakana.shepherd.Shepherd(self._threadManager, port) + + def startCooja(self): + # COMPILE COOJA AND PLUGIN + if self._doCompile == "True": + for target in ("cooja", "plugin"): + buildCommand = 'cd ' + chakana.command.quote(self._chakanaRoot) + ' && ant compile_' + target + debug(MajorEvent, "Building " + target) + debug(Event, buildCommand) + output = chakana.command.output(buildCommand) + debug(MinorEvent, output) + + coojaOutputFile = os.path.join(self._chakanaRoot, "build/cooja.out") + # START COOJA + + if os.path.isfile(coojaOutputFile): + os.remove(coojaOutputFile) + coojaThread = CoojaThread(self._threadManager, self._chakanaRoot, + coojaOutputFile, self._coojaTimeout, withGUI=self._withGUI) + coojaThread.start() + return coojaThread.port() + + def shepherd(self): + return self._shepherd + + def quit(self): + self._shepherd.quit() + + def killAllProcesses(self): + self._threadManager.killAll() + + def waitForAllThreads(self, timeout): + self._threadManager.waitAll(timeout) + +class CoojaThread(chakana.threads.ManagedThread): + def __init__(self, threadManager, chakanaRoot, outputFile, + timeout = 3600, name = "COOJA", withGUI="False", ** kwArgs): + chakana.threads.ManagedThread.__init__( + self, threadManager, name = name, **kwArgs) + self._chakanaRoot = chakanaRoot + self._outputFile = outputFile + self._timeout = timeout + self._port = None + self._withGUI = withGUI + + def doRun(self): + debug(MajorEvent, "Starting COOJA") + buildDir = os.path.dirname(self._outputFile) + chakana.utils.makeDirsSafe(buildDir) + contikiRoot = os.path.join(self._chakanaRoot, '../..') + contikiRoot = contikiRoot.replace('/cygdrive/c', 'c:') + if self._withGUI == "True": + coojaCommand = '( cd ' + chakana.command.quote(buildDir) + ' && java -jar ' + \ + chakana.command.quote(os.path.join(contikiRoot, 'tools/cooja/dist/cooja.jar')) + ' ' + \ + '-external_tools_config=../cooja.chakana.properties ' + \ + '-contiki=' + chakana.command.quote(contikiRoot) + ' < /dev/null > ' + \ + os.path.basename(self._outputFile) + ' 2>&1 )' + else: + coojaCommand = '( cd ' + chakana.command.quote(buildDir) + ' && java -jar ' + \ + chakana.command.quote(os.path.join(contikiRoot, 'tools/cooja/dist/cooja.jar')) + ' ' + \ + '-nogui ' + \ + '-external_tools_config=../cooja.chakana.properties ' + \ + '-contiki=' + chakana.command.quote(contikiRoot) + ' < /dev/null > ' + \ + os.path.basename(self._outputFile) + ' 2>&1 )' + + debug(Event, coojaCommand) + os.system(coojaCommand) + debug(MajorEvent, "COOJA has finished") + + def port(self): + if self._port is None: + laps = 0 + debug(Event, "Waiting for COOJA to open server socket") + debug(Debug, "Reading: " + self._outputFile) + while 1: + if self._timeout > 0 and laps > self._timeout: + raise chakana.error.Timeout(self, self._timeout) + logContents = "" + try: + logContents = chakana.utils.readFile(self._outputFile) + except IOError, err: + if err.errno != errno.ENOENT: + raise + match = re.search(r"Chakana server listening on port (\d+).", + logContents) + debug(Debug, "Log contents: " + logContents) + if match: + self._port = int(match.group(1)) + debug(Event, "COOJA is now listening on port " + str(self._port)) + break + else: + debug(Debug, "Waiting for COOJA to start") + time.sleep(1) + laps += 1 + + match = re.search(r"Unable to access jarfile", + logContents) + if match: + raise RuntimeError("Could not locate COOJA JAR: " + logContents) + + return self._port + diff --git a/tools/chakana/linux.py b/tools/chakana/linux.py new file mode 100644 index 000000000..087eb5c49 --- /dev/null +++ b/tools/chakana/linux.py @@ -0,0 +1,268 @@ +# +# Copyright (C) 2004-2007 Swedish Institute of Computer Science. +# +# Please refer to the file named LICENSE in the same directory as this +# file for licensing information. +# + + +# $Id: linux.py,v 1.1 2007/08/21 14:41:01 fros4943 Exp $ + +import errno +import glob +import os +import re +import signal +import string +import time + +import chakana.command +import chakana.error +import chakana.utils + +from debug import * + +def numProcessors(physical = 0, cpuInfo = None): + return Host().CpuInfo(cpuInfo).count(physical = physical) + +class CpuInfo: + "Parser for /proc/cpuinfo." + def __init__(self, cpuInfo = None): + if cpuInfo is None: + cpuInfo = chakana.utils.readFile("/proc/cpuinfo") + self.__values = [map(string.strip, l.split(":")) + for l in cpuInfo.splitlines()] + + def values(self): + return self.__values + + def find(self, name): + debug(Debug3, "Looking for " + name + " in cpuinfo:") + ret = [val[1] for val in self.values() if val[0] == name] + debug(Debug3, repr(ret)) + return ret + +class IfconfigInfo: + "Parser for ifconfig output." + def __init__(self, ifconfigOutput = None): + if ifconfigOutput is None: + self._ifconfigOutput = chakana.command.output("/sbin/ifconfig") + else: + self._ifconfigOutput = ifconfigOutput + + def interfaces(self): + return map(InterfaceInfo, + re.compile("^\\w+", re.M).findall(self._ifconfigOutput)) + +class InterfaceInfo: + "Parser for ifconfig output for a single interface." + def __init__(self, interface = None, ifconfigOutput = None): + if interface is None: + self._interface = chakana.command.output("/sbin/ifconfig").split()[0] + else: + self._interface = interface + if ifconfigOutput is None: + self._ifconfigOutput = chakana.command.output("/sbin/ifconfig " + + self._interface) + else: + self._ifconfigOutput = ifconfigOutput + + def name(self): + return self._ifconfigOutput.split()[0] + + def address(self): + match = re.search("inet addr:((\d{1,3}\.){3}\d{1,3})", + self._ifconfigOutput) + if match: + return match.group(1) + else: + return None + + __str__ = name + +class MemInfo(dict): + "Parser for /proc/meminfo" + def __init__(self, memInfo = None): + if memInfo is None: + memInfo = chakana.utils.readFile("/proc/meminfo") + for line in memInfo.splitlines(): + if len(line.split(":")) == 2: + key, value = line.split(":") + if len(value.strip().split()) <= 2: + if len(value.strip().split()) == 2: + unit = {"KB" : 1024, "MB" : 1024 * 1024, + "GB" : 1024 * 1024 * 1024 }[value.split()[-1].upper()] + else: + unit = 1 + self[key] = long(value.strip().split()[0]) * unit + +class UnameInfo: + "Parser for output from uname -a" + def __init__(self, unameInfo = None): + if unameInfo is None: + unameInfo = chakana.command.output("uname -a") + self._unameInfo = unameInfo.split() + + def __getitem__(self, i): + return self._unameInfo[i] + + def __len__(self): + return len(self._unameInfo) + + def __getattr__(self, attr): + ret = self[self.indexMap()[attr]] + if (attr == "architecture") and \ + (re.match(r"(.*intel|i[0-9]86)", ret, re.I)): + # Work around bogus uname output from e.g. Gentoo + return "i386" + else: + return ret + + def smp(self): + return "SMP" in self._unameInfo + + def indexMap(self): + return { + "os" : 0, + "host" : 1, + "version" : 2, + "architecture" : -2, + } + + def __str__(self): + return repr(dict([(attr, getattr(self, attr)) + for attr in self.indexMap()])) + + def __repr__(self): + return repr(self._unameInfo) + +class X86: + class CpuInfo(CpuInfo): + def count(self, physical = 0): + logical = len(self.find("processor")) + if physical: + siblings = self.find("siblings") + if len(siblings) > 0: + return logical / int(siblings[0]) + return logical + + def type(self, num = 0): + return re.match(r"Intel(\(R\))? (.*) processor [\d]+MHz", + self.find("model name")[num]).group(2) + +class X86_64(X86): + pass + +class Sparc: + class CpuInfo(CpuInfo): + def count(self): + return int(self.find("ncpus active")[0]) + + def type(self, num = 0): + return " ".join(self.find("cpu")[num].split()[1:3]) + + class UnameInfo(UnameInfo): + pass + +def freeSpace(path): + "Return available disk space on device corresponding to path." + # If the device name is long, df may split the relevant line, so + # count lines and words from end. + return long( + chakana.command.output("df -k " + path).splitlines()[-1].split()[-3]) * 1024 + +def macAddresses(): + "Return list of mac addresses for all network interfaces." + return re.findall('..:..:..:..:..:..', os.popen( + '/sbin/ifconfig -a | grep HWaddr', 'r').read().lower()) + +class Process: + """Representation of a running process.""" + + def __init__(self, pid): + self._pid = pid + + def pid(self): + return self._pid + + def attribute(self, name): + try: + lines = chakana.utils.readFile("/proc/" + str(self._pid) + + "/status").splitlines() + for line in lines: + (key, value) = line.split(":", 1) + if key.strip().lower() == name.lower(): + return value.strip() + except EnvironmentError, err: + if err.errno == errno.ENOENT: + raise chakana.error.errnoError(errno.ESRCH) + raise + + def name(self): + return self.attribute("name") + + def parent(self): + return Process(int(self.attribute("ppid"))) + + def children(self): + ret = [] + for proc in self.all(): + try: + if proc.parent().pid() == self.pid(): + ret.append(proc) + except EnvironmentError, err: + if err.errno != errno.ESRCH: + raise + return ret + + def reallyKill(self, graceTime = 1, sigKillTime = 3, debugLevel = Event): + """Really kill process after waiting for graceTime. Send SIGKILL after + sigKillTime.""" + try: + debug(debugLevel, "Killing process " + str(self.pid()) + + " (" + str(self.name()) + ")") + os.kill(self.pid(), 0) + time.sleep(graceTime) + debug(debugLevel + 1, "kill -TERM " + str(self.pid())) + os.kill(self.pid(), signal.SIGTERM) + for tick in range(sigKillTime): + os.kill(self.pid(), 0) + debug(debugLevel + 1, "kill -KILL " + str(self.pid())) + os.kill(self.pid(), signal.SIGKILL) + time.sleep(graceTime) + if self.attribute("state")[0] != "Z": + debug(debugLevel, "Process refused to die: " + str(self.pid()) + + " (" + self.name() + ")") + except EnvironmentError, err: + if err.errno != errno.ESRCH: + raise + debug(debugLevel + 2, "Pid " + str(self.pid()) + " no longer exists") + + def killAllBelow(self, debugLevel = Event, ** kwArgs): + """Kill all processes below (but not including) a process. Start from + bottom of tree.""" + debug(debugLevel, "Killing all processes below " + str(self.pid()) + + " (" + self.name() + ")") + children = self.children() + debug(debugLevel + 2, "Pid " + str(self.pid()) + " has " + + str(len(children)) + " children") + for child in children: + child.killAllBelow(debugLevel = debugLevel, ** kwArgs) + child.reallyKill(debugLevel = debugLevel, ** kwArgs) + + def all(cls): + ret = [] + for statusFile in glob.glob("/proc/*/status"): + try: + ret.append(cls(int(os.path.basename(os.path.dirname(statusFile))))) + except ValueError: + continue + return ret + all = classmethod(all) + +def Host(): + architecture = chakana.command.output("uname -m").strip() + if re.match("i[0-9]86", architecture): + return X86 + elif architecture == "x86_64": + return X86_64 diff --git a/tools/chakana/monitor.py b/tools/chakana/monitor.py new file mode 100644 index 000000000..24675bfeb --- /dev/null +++ b/tools/chakana/monitor.py @@ -0,0 +1,184 @@ +import Queue +import thread + +import chakana.event +import chakana.threads +from chakana.debug import * + +class Monitor(chakana.threads.ManagedThread): + """Base class for causal path monitors. Pass the threadManager to the + constructor. Override the doRun method or pass a routine argument. In + the latter case, the routine will be called with the Monitor object as + argument plus any extra arguments and keyword arguments specified.""" + + def __init__(self, shepherd, routine = None, * args, ** kwArgs): + chakana.threads.ManagedThread.__init__(self, shepherd.threadManager()) + self._shepherd = shepherd + self._routine = routine + self._args = args + self._kwArgs = kwArgs + self._ready = 0 + self._discarded = 0 + resourceAllocated = 0 + while resourceAllocated == 0: + try: + self._isWaitingMonitor = threading.Event() + resourceAllocated = 1 + except thread.error: + resourceAllocated = 0 + shepherd.threadManager().registerPIDerror() + debug(Debug, "Monitor created") + self._shepherd.registerMonitor(self) + + def startMonitor(self): + self.start() + debug(Debug, "New monitor thread started: " + repr(self)) + + def discard(self): + self._discarded = 1 + self._ready = 0 + debug(Debug, "Discarding monitor:\n" + repr(self)) + + # Wake up thread with empty results + if hasattr(self, "_resultQueue"): + self._resultQueue.put(None) + + def waitMonitor(self, parentEventpoint = None): + debug(Debug, "Waiting for monitor to finish: " + repr(self)) + + # While monitor is registered, check for continuations + while self in self.shepherd()._monitors: + resourceAllocated = 0 + while resourceAllocated == 0: + try: + self._isWaitingMonitor.wait() + resourceAllocated = 1 + except thread.error: + resourceAllocated = 0 + self.shepherd().threadManager().registerPIDerror() + + self._isWaitingMonitor.clear() + self._ready = 1 + + # If all monitors ready, poll continuation + allMonitorsReady = 1 + for monitor in self.shepherd()._monitors: + if not monitor._ready: + allMonitorsReady = 0 + if allMonitorsReady: + if self in self.shepherd()._monitors: + self.shepherd().pollContinuation() + else: + # A monitor may be waiting for us, tell parent we are ready + if parentEventpoint is not None: + parentEventpoint._isWaitingEvent.set() + self.join() + + def doRun(self): + debug(Debug, "Running monitor") + try: + if (not self._discarded): + self.runMonitor() + except chakana.error.Discarded: + pass + self.shepherd().unregisterMonitor(self) + self._isWaitingMonitor.set() + debug(Debug, "Monitor exiting") + + def runMonitor(self): + self._routine(self, * self._args, ** self._kwArgs) + + def waitFor(self, eventList): + """Waits until one of the Events given in 'eventList' have triggered and + returns that Event.""" + if isinstance(eventList, chakana.event.Eventpoint): + eventList = [eventList] + debug(MinorEvent, "Waiting for events to trigger. Event list: " + + str(eventList)) + assert eventList != [] + + # Clean up eventpoints and abort monitor if discarded + if self._discarded: + for ev in eventList: + ev.discard() + raise chakana.error.Discarded() + + self.shepherd().registerWaitingMonitor(self) + + resourceAllocated = 0 + while resourceAllocated == 0: + try: + self._resultQueue = Queue.Queue() + resourceAllocated = 1 + except thread.error: + resourceAllocated = 0 + self.shepherd().threadManager().registerPIDerror() + + for ev in eventList: + # Start thread and wait until eventpoint is waiting + ev.await(self._resultQueue) + + resourceAllocated = 0 + while resourceAllocated == 0: + try: + ev._isWaitingEvent.wait() + resourceAllocated = 1 + except thread.error: + resourceAllocated = 0 + self.shepherd().threadManager().registerPIDerror() + + ev._isWaitingEvent.clear() + + debug(Event, "Waiting for events: " + repr(eventList)) + + assert(not self._discarded) + + self._isWaitingMonitor.set() + resourceAllocated = 0 + while resourceAllocated == 0: + try: + triggeredEvent = self._resultQueue.get() + resourceAllocated = 1 + except thread.error: + resourceAllocated = 0 + self.shepherd().threadManager().registerPIDerror() + + self._ready = 0 + self.shepherd().unregisterWaitingMonitor(self) + debug(Event, "Eventpoint was triggered: " + repr(eventList)) + for ev in eventList: + if not (ev is triggeredEvent): + ev.discard() + + return triggeredEvent + + def shepherd(self): + return self._shepherd + + def readVariable(self, ** kwArgs): + return self.shepherd().readVariable(** kwArgs) + + def readInt(self, ** kwArgs): + return self.shepherd().readInt(** kwArgs) + + def readMemory(self, ** kwArgs): + return self.shepherd().readMemory(** kwArgs) + +class TimeoutWrapperMonitor(Monitor): + def __init__(self, shepherd, routine, eventpoint = None, timeout = 1000): + chakana.monitor.Monitor.__init__(self, shepherd) + self._eventpoint = eventpoint + self._timeout = timeout + self._triggeredEventpoint = None + + def runMonitor(self): + timeEventpoint = chakana.event.TimeEventpoint(self.shepherd(), self._timeout) + self._triggeredEventpoint = self.waitFor([timeEventpoint, self._eventpoint]) + + def getTriggeredEventpoint(self): + return self._triggeredEventpoint + + def timedOut(self): + return self.getTriggeredEventpoint() != self._eventpoint + + \ No newline at end of file diff --git a/tools/chakana/shepherd.py b/tools/chakana/shepherd.py new file mode 100644 index 000000000..89b6f1986 --- /dev/null +++ b/tools/chakana/shepherd.py @@ -0,0 +1,344 @@ +import re +import socket +import threads +import xml.dom.minidom +import thread + +import chakana.error +import chakana.event +import chakana.monitor +import chakana.threads +import chakana.utils +from chakana.debug import * + +class Shepherd: + """The shepherd is a singleton class that manages the connection to COOJA + and keeps track of user monitor threads. The user initialises a + debugging scenario by creating a Shepherd object, and then calls the + newMonitor method to create Monitor objects, one for each causal + path through the system that he wishes to monitor. + + The monitor threads automatically register with the shepherd. The user + can use the debugger instances to probe variables or create eventpoints. + When the monitor thread routine decides that is ready for simulation to + proceed, it calls the waitFor method to block until one of the + eventpoints occur. + + When a Monitor object enters waitFor, it informs shepherd about the + eventpoints that the user is waiting for, and waits for the eventpoints + to happen. When all threads enter waiting state, the shepherd asks COOJA + to resume simulation. When an eventpoint is hit, the shepherd wakes the + Monitor that is waiting for that eventpoint. + + """ + + def __init__(self, threadManager, port, coojaHost = "localhost"): + debug(MajorEvent, "Creating shepherd") + self._pollConnLock = threading.Lock() + self._threadManager = threadManager + self._port = port + self._monitors = [] + self._waitingMonitors = [] + # Only basic eventpoints in these lists + self._eventpoints = [] + self._waitingEventpoints = [] + self._coojaContinueEvent = threading.Event() + self._waitMap = {} + self._coojaHost = coojaHost + self._coojaConnection = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + debug(MajorEvent, "Connecting to COOJA at " + coojaHost + ":" + + str(self._port)) + self._connectionLock = threading.Lock() + self._coojaConnection.connect((self._coojaHost, self._port)) + self._coojaStream = self._coojaConnection.makefile() + self._coojaConnection.close() + del self._coojaConnection + self._connectionLock.acquire() + hello = self.readFromCooja() + debug(Debug, hello.toprettyxml()) + self._connectionLock.release() + self._shepherdThread = threads.ManagedThread( + self._threadManager, target = self._shepherdMainLoop) + self._shepherdThread.start() + debug(Debug, "Shepherd created:\n" + repr(self)) + + def _shepherdMainLoop(self): + debug(Event, "Starting shepherd main loop") + while 1: + debug(Debug, "Waiting for eventpoints to enter wait state") + debug(Debug2, repr(self)) + self._coojaContinueEvent.wait() + self._coojaContinueEvent.clear() + if self._coojaStream.closed: + debug(Event, "Terminating shepherd main loop") + return + debug(Debug, "Resuming COOJA") + assert(len(self._eventpoints) == len(self._waitingEventpoints)) + response = self.runCommand("CONTROL_SIM", { "RESUME" : None }) + id = int(response.documentElement.childNodes[0].nodeValue) + debug(MinorEvent, "Eventpoint " + str(id) + " was triggered") + + # Wake up triggered eventpoint + self._waitMap[id][1].set() + + # Allow new main loop startups + self._pollConnLock.release() + + + def quit(self): + """Send a termination request to the debugger broker, and discard all + debuggers.""" + + debug(Information, "Terminating the shepherd session") + self.runCommand("EXIT_COOJA") + self._connectionLock.acquire() + self._coojaStream.close() + self._connectionLock.release() + self._coojaContinueEvent.set() + debug(Information, "Number of PID errors reported: " + str(self.threadManager().nrPIDerrors())) + + for (eventpoint, event) in self._waitMap.values(): + debug(Debug2, "Awakening eventpoint at exit: " + repr(eventpoint)) + event.set() + + def loadConfiguration(self, fileName): + return self.runCommand( + "CREATE_SIM", xmlContent = chakana.utils.readFile(fileName)) + + def loadConfigurationXML(self, xmlContent): + return self.runCommand( + "CREATE_SIM", xmlContent = xmlContent) + + def setConfigurationXML(self, xmlContent): + return self.runCommand( + "CONF_SIM", xmlContent = xmlContent) + + def setPluginsXML(self, xmlContent): + return self.runCommand( + "CONF_PLUGINS", xmlContent = xmlContent) + + def newMonitor(self, routine, * args, ** kwArgs): + return self.newCustomMonitor( + chakana.monitor.Monitor, routine, * args, ** kwArgs) + + def newCustomMonitor(self, Type, routine = None, * args, ** kwArgs): + """Start a new monitor thread running routine, with * args and ** kwArgs + as arguments.""" + debug(Debug, "Creating new monitor of type " + Type.__name__) + debug(Debug3, "Routine: " + repr(routine) + ", arguments: " + repr(args) + + ", keyword arguments: " + repr(kwArgs)) + thread = Type(self, routine, * args, ** kwArgs) + debug(MinorEvent, "New monitor thread created: " + repr(thread)) + return thread + + def newTimeoutMonitor(self, eventpoint, timeout, routine = None): + return self.newCustomMonitor( + chakana.monitor.TimeoutWrapperMonitor, routine, eventpoint = eventpoint, timeout = timeout) + + def threadManager(self): + return self._threadManager + + def registerMonitor(self, monitor): + debug(Debug, "Registering monitor: " + repr(monitor)) + assert(isinstance(monitor, chakana.monitor.Monitor)) + assert(not (monitor in self._monitors)) + assert(not (monitor in self._waitingMonitors)) + self._monitors.append(monitor) + self._monitors.sort() + debug(Debug2, repr(self)) + + def unregisterMonitor(self, monitor): + debug(Debug, "Unregistering monitor: " + repr(monitor)) + assert(isinstance(monitor, chakana.monitor.Monitor)) + assert(monitor in self._monitors) + assert(not (monitor in self._waitingMonitors)) + self._monitors.remove(monitor) + + def registerWaitingMonitor(self, monitor): + debug(Debug, "Registering waiting monitor: " + repr(monitor)) + assert(isinstance(monitor, chakana.monitor.Monitor)) + assert(monitor in self._monitors) + assert(not (monitor in self._waitingMonitors)) + self._waitingMonitors.append(monitor) + self._waitingMonitors.sort() + debug(Debug2, repr(self)) + + def unregisterWaitingMonitor(self, monitor): + debug(Debug, "Unregistering waiting monitor: " + repr(monitor)) + assert(isinstance(monitor, chakana.monitor.Monitor)) + assert(monitor in self._monitors) + assert(monitor in self._waitingMonitors) + self._waitingMonitors.remove(monitor) + debug(Debug2, repr(self)) + + def registerBasicEventpoint(self, eventpoint): + debug(Debug2, "Registering basic eventpoint: " + repr(eventpoint)) + assert(isinstance(eventpoint, chakana.event.BasicEventpoint)) + assert(not (eventpoint in self._eventpoints)) + self._eventpoints.append(eventpoint) + self._eventpoints.sort() + debug(Debug2, repr(self)) + + def unregisterBasicEventpoint(self, eventpoint): + debug(Debug2, "Unregistering basic eventpoint: " + repr(eventpoint)) + assert(not (eventpoint in self._waitingEventpoints)) + assert(eventpoint in self._eventpoints) + self._eventpoints.remove(eventpoint) + debug(Debug2, repr(self)) + + def _registerWaitingEventpoint(self, eventpoint): + debug(Debug2, "Registering waiting eventpoint: " + repr(eventpoint)) + assert(not (eventpoint in self._waitingEventpoints)) + assert(eventpoint in self._eventpoints) + self._waitingEventpoints.append(eventpoint) + self._waitingEventpoints.sort() + + def _unregisterWaitingEventpoint(self, eventpoint): + debug(Debug2, "Unregistering waiting eventpoint: " + repr(eventpoint)) + assert(eventpoint in self._waitingEventpoints) + assert(eventpoint in self._eventpoints) + self._waitingEventpoints.remove(eventpoint) + debug(Debug2, repr(self)) + + def pollContinuation(self): + self._pollConnLock.acquire() + if len(self._waitingMonitors) != len(self._monitors): + self._pollConnLock.release() + return + for monitor in self._monitors: + if not monitor._ready: + self._pollConnLock.release() + return + + for monitor in self._monitors: + assert(not monitor._discarded) + + for eventpoint in self._eventpoints: + assert(not eventpoint._discarded) + + assert(self._waitingMonitors == self._monitors) + assert(self._waitingEventpoints == self._eventpoints) + debug(MinorEvent, "All monitors are waiting, activating COOJA") + assert(not self._coojaContinueEvent.isSet()) + self._coojaContinueEvent.set() + + def waitForCoojaEventpoint(self, eventpoint): + response = self.runCommand("ADD_EVENTPOINT", eventpoint.coojaArguments()) + id = int(response.documentElement.childNodes[0].nodeValue) + debug(Debug, "Waiting for COOJA eventpoint " + str(id)) + + resourceAllocated = 0 + while resourceAllocated == 0: + try: + self._waitMap[id] = (eventpoint, threading.Event()) + resourceAllocated = 1 + except thread.error: + resourceAllocated = 0 + self.threadManager().registerPIDerror() + + self._registerWaitingEventpoint(eventpoint) + eventpoint._isWaitingEvent.set() + + resourceAllocated = 0 + while resourceAllocated == 0: + try: + self._waitMap[id][1].wait() + resourceAllocated = 1 + except thread.error: + resourceAllocated = 0 + self.threadManager().registerPIDerror() + + del self._waitMap[id] + self._unregisterWaitingEventpoint(eventpoint) + try: + self.runCommand("DELETE_EVENTPOINT", { "id" : id }) + debug(Debug, "COOJA event " + str(id) + " has occurred") + except chakana.error.CoojaExit: + pass + except socket.error: + pass + + def runCommand(self, name, args = None, xmlContent = ""): + debug(Debug3, "Running cooja command " + name + ", args: " + repr(args) + + " xml content: " + repr(xmlContent)) + if args is None: + args = {} + self._connectionLock.acquire() + try: + if self._coojaStream.closed: + raise chakana.error.CoojaExit() + command = [''] + for (key, value) in args.items(): + if value is None: + command.append('<' + key + '/>') + else: + command.append('<' + key + '>' + str(value) + '') + command.append(xmlContent + '') + commandStr = "\n".join(command + ['\n']) + debug(MinorEvent, '--> ' + commandStr) + self._coojaStream.write(commandStr) + self._coojaStream.flush() + response = self.readFromCooja() + debug(Debug, response.toprettyxml()) + if response.documentElement.tagName == 'error': + raise chakana.error.CoojaError(response) + return response + finally: + self._connectionLock.release() + + def readFromCooja(self): + # XXX: Assume message ends with a newline + debug(Debug, "Reading message from COOJA") + responseLines = [self._coojaStream.readline()] + debug(Debug2, "First line: " + repr(responseLines[0])) + (rootElement, slash) = re.match(r"^\s*<(\w+)(/?)>", + responseLines[0]).groups() + debug(Debug3, "Root element: " + rootElement) + if slash != "/": + while 1: + if re.search("$", responseLines[-1], + re.M): + break + responseLines.append(self._coojaStream.readline()) + debug(Debug3, "Read line: " + repr(responseLines[-1])) + result = "".join(responseLines) + debug(MinorEvent, '<-- ' + result) + return xml.dom.minidom.parseString(result) + + def readVariable(self, variable, ** kwArgs): + response = self.readMemory( + type = "variable", variable = variable, ** kwArgs) + arrayString = response.documentElement.childNodes[0].nodeValue.split() + bytes = map(eval, arrayString) + debug(Debug, "Read variable " + variable + " as byte array: " + + repr(bytes)) + return bytes + + def readInt(self, ** kwArgs): + response = self.readMemory(type = "int", ** kwArgs) + return eval(response.documentElement.childNodes[0].nodeValue) + + def readMemory(self, ** kwArgs): + return self.runCommand("READ_MEMORY", kwArgs) + + def killNodesInInterval(self, ** kwArgs): + return self.runCommand("KILL_NODES", kwArgs) + + def sendCustomCommand(self, ** kwArgs): + return self.runCommand("CUSTOM_COMMAND", kwArgs) + + def getSimulationInfo(self, ** kwArgs): + response = self.runCommand("GET_INFO", kwArgs) + return eval(response.documentElement.childNodes[0].nodeValue) + + def __repr__(self): + return "\n ".join([ + "Shepherd:", + "Port: " + repr(self._port), + "Monitors: " + repr(self._monitors), + "Waiting monitors: " + repr(self._waitingMonitors), + "Basic eventpoints: [" + "\n".join(map(repr, self._eventpoints)) + "]", + "Waiting eventpoints: [" + "\n".join( + map(repr, self._waitingEventpoints)) + "]", + "Wait map: " + repr(self._waitMap), + ]) diff --git a/tools/chakana/threads.py b/tools/chakana/threads.py new file mode 100644 index 000000000..4f102d3f7 --- /dev/null +++ b/tools/chakana/threads.py @@ -0,0 +1,287 @@ +# +# Copyright (C) 2005-2007 Swedish Institute of Computer Science. +# +# Please refer to the file named LICENSE in the same directory as this file +# for licensing information. +# +# Written and maintained by Lars Albertsson . +# + +# $Id: threads.py,v 1.1 2007/08/21 14:41:01 fros4943 Exp $ + +import signal +import sys +import threading +import time +import Queue +import thread + +import chakana.error +import chakana.linux +from chakana.debug import * + +class ManagedThread(threading.Thread): + def __init__(self, threadManager, ** kwArgs): + self._threadManager = threadManager + self._exceptionInfo = None + resourceAllocated = 0 + while resourceAllocated == 0: + try: + threading.Thread.__init__(self, ** kwArgs) + resourceAllocated = 1 + except thread.error: + resourceAllocated = 0 + self.threadManager().registerPIDerror() + + debug(Event, "Initialising thread " + self.getName()) + self._threadManager._registerThread(self) + + def threadManager(self): + return self._threadManager + + def start(self): + try: + self._threadManager + except: + raise RuntimeError("ManagedThread started, but not initialised properly") + return threading.Thread.start(self) + + def run(self): + debug(Event, self.getName() + " thread is now running") + self.threadManager()._registerThreadStart(self) + try: + self.doRun() + except Exception, err: + debug(MajorEvent, "Caught exception in " + self.getName() + " thread" + + ":\n" + exceptionDump(sys.exc_info())) + self._exceptionInfo = sys.exc_info() + except: + debug(MajorEvent, "Uncaught exception in " + self.getName() + " thread") + self._exceptionInfo = sys.exc_info() + + if not self._exceptionInfo is None: + debug(Event, "Exception caught:\n" + exceptionDump(self._exceptionInfo)) + + debug(Event, self.getName() + " thread is terminating") + if not self.getName() == 'COOJA' and self._exceptionInfo is None: + debug(MinorEvent, self.getName() + " terminated quietly") + self.threadManager()._listLock.acquire() + del self.threadManager()._startedThreads[self.getName()] + del self.threadManager()._activeThreads[self.getName()] + self.threadManager()._listLock.release() + else: + debug(MinorEvent, self.getName() + " terminated normally (slow)") + self.threadManager()._registerResult(self, self._exceptionInfo) + + debug(Debug, "End of " + self.getName() + " thread") + + def doRun(self): + threading.Thread.run(self) + + +class ThreadManager(object): + """Singleton class for managing active python threads.""" + def __init__(self): + self._activeThreads = {} + self._startedThreads = {} + self._threadTerminations = [] + self._pidErrors = 0 + # Protects the three lists above + self._listLock = threading.RLock() + self._threadTerminationQueue = Queue.Queue() + debug(Debug, self.summary()) + + def _registerThread(self, thread): + "Called by thread classes when a thread is created." + assert(isinstance(thread, ManagedThread)) + self._listLock.acquire() + try: + assert(not thread.getName() in self._activeThreads) + debug(Debug, "Registering " + thread.getName() + " thread") + self._activeThreads[thread.getName()] = thread + debug(Debug, self.summary()) + finally: + self._listLock.release() + + def _registerThreadStart(self, thread): + "Called by the thread classes' run method." + assert(isinstance(thread, ManagedThread)) + self._listLock.acquire() + try: + assert(thread.getName() in self._activeThreads) + assert(not thread.getName() in self._startedThreads) + debug(Debug, "Registering " + thread.getName() + " thread start") + self._startedThreads[thread.getName()] = thread + debug(Debug, self.summary()) + finally: + self._listLock.release() + + def _registerResult(self, thread, exceptionInfo): + self._threadTerminationQueue.put((thread.getName(), exceptionInfo)) + + def runningThreads(self): + self._listLock.acquire() + try: + ret = [] + for thread in self._startedThreads.values(): + if not thread.getName() in [ + te[0].getName() for te in self._threadTerminations]: + ret.append(thread) + return ret + finally: + self._listLock.release() + + def unstartedThreads(self): + self._listLock.acquire() + try: + ret = [] + for thread in self._activeThreads.values(): + if not thread.getName() in self._startedThreads: + ret.append(thread) + return ret + finally: + self._listLock.release() + + def numThreadsLeft(self): + return len(self.unfinishedThreads()) + + def unfinishedThreads(self): + "Return created threads that have not terminated." + self._listLock.acquire() + try: + return self._activeThreads.values() + finally: + self._listLock.release() + + def waitAll(self, timeout = 3600, mourningTime = 5): + "Wait for all threads to terminate." + debug(MajorEvent, "Waiting for all test threads to terminate") + debug(MinorEvent, self.summary()) + limit = time.time() + timeout + while self.numThreadsLeft() > 0: + try: + nextTimeout = limit - time.time() + if nextTimeout <= 0: + raise chakana.error.Timeout(self.unfinishedThreads()[0], timeout) + else: + self.waitOne(nextTimeout) + except chakana.error.Timeout, err: + debug(Error, "Timeout waiting for " + err.child.getName() + + " thread, killing subprocesses.") + self.killAll() + while self.numThreadsLeft() > 0: + try: + self.waitOne(mourningTime) + except chakana.error.Timeout: + debug(Error, "Timeout while mourning threads, aborting") + signal.signal(signal.SIGABRT, signal.SIG_DFL) + os.abort() + raise + debug(MinorEvent, "Done waiting for " + str(len(self._startedThreads)) + + " threads") + debug(Debug, self.summary()) + for (thread, excInfo) in self._threadTerminations: + if not excInfo is None: + debug(MinorEvent, "Rethrowing exception from " + thread.getName() + + " thread") + raise chakana.error.ChildException(excInfo[1], excInfo) + + def waitOne(self, timeout): + "Wait for any one thread." + debug(Event, "Waiting for some thread to finish, timeout = " + + str(timeout)) + debug(MinorEvent, self.summary()) + debug(Debug, "Threads left: " + + str(map(ManagedThread.getName, self.unfinishedThreads()))) + assert(self.numThreadsLeft() > 0) + try: + (threadName, exception) = self._threadTerminationQueue.get( + timeout = timeout) + except Queue.Empty: + raise chakana.error.Timeout(self.unfinishedThreads()[0], timeout) + debug(MinorEvent, "Received termination signal from thread " + threadName) + self._listLock.acquire() + try: + assert(threadName in self._activeThreads) + terminatedThread = self._activeThreads[threadName] + assert(terminatedThread.getName() == threadName) + if exception is None: + debug(Debug, "Thread " + threadName + " completed successfully") + else: + debug(Debug, "Thread " + threadName + " raised an exception") + self._threadTerminations.append((terminatedThread, exception)) + self.killAll() + terminatedThread.join() + debug(Debug, "Deleting " + threadName + " thread from active threads") + del self._activeThreads[threadName] + del self._startedThreads[threadName] + finally: + self._listLock.release() + + def killAll(self, reason = "Error detected, killing remaining processes"): + debug(MajorEvent, reason) + chakana.linux.Process(os.getpid()).killAllBelow() + + def registerPIDerror(self): + self._pidErrors = self._pidErrors + 1 + if self._pidErrors > 100000: + self.killAll(reason = "PID allocation errors > 100000") + + def nrPIDerrors(self): + return self._pidErrors + + def summary(self): + return "ThreadManager: " + str(self.numThreadsLeft()) + \ + " threads left, " + \ + str(len(self.unstartedThreads())) + " unstarted threads,\n" + \ + str(len(self.runningThreads())) + " running threads" + +class TimeoutHelperThread(threading.Thread): + def __init__(self, func, funcArgs, funcKwArgs, ** kwArgs): + threading.Thread.__init__(self, ** kwArgs) + self._func = func + self._args = funcArgs + self._kwArgs = funcKwArgs + self.result = None + self.error = None + + def run(self): + try: + self.result = self._func(* self._args, ** self._kwArgs) + except Exception, err: + debug(Debug, "Caught exception in timeout function: " + str(err) + + ":\n" + exceptionDump(sys.exc_info())) + self.error = err + self.exc_info = sys.exc_info() + +class RunWithTimeout: + """Run func in a separate thread. If timeout seconds elapse, give + up, raise Timeout(thread, timeout)""" + + def __init__(self, timeout, func, name = None): + self._timeout = timeout + self._func = func + if name is None: + self._name = "thread running " + str(func) + else: + self._name = name + + def __call__(self, * args, ** kwArgs): + thread = TimeoutHelperThread(self._func, args, kwArgs, name = self._name) + thread.start() + thread.join(self._timeout) + if thread.isAlive(): + raise chakana.error.Timeout(thread, self._timeout) + if thread.error is None: + return thread.result + raise chakana.error.ChildException(thread.error, thread.exc_info) + +def waitForAll(threads, timeout): + startTime = time.time() + for t in threads: + if timeout is None: + t.join() + else: + t.join(startTime + timeout - time.time()) + if t.isAlive(): + raise chakana.error.Timeout(t, timeout) diff --git a/tools/chakana/utils.py b/tools/chakana/utils.py new file mode 100644 index 000000000..bc8a433c4 --- /dev/null +++ b/tools/chakana/utils.py @@ -0,0 +1,736 @@ + +# Copyright (C) 2003-2007 Swedish Institute of Computer Science. +# +# Please refer to the file named LICENSE in the same directory as this +# file for licensing information. +# + + +# $Id: utils.py,v 1.1 2007/08/21 14:41:01 fros4943 Exp $ + +import errno +import math +import operator +import os +import popen2 +import re +import string +import shutil +import sys +import tempfile +import threading +import traceback +import types + +import chakana.command +import chakana.error +from chakana.debug import * + +try: + global False + global True + _ = False +except NameError: + False = 0 + True = 1 + +class DefaultDict(dict): + def __init__(self, defaultCreator, initialiser = (), inject = 0): + dict.__init__(self, initialiser) + if callable(defaultCreator): + self._defaultCreator = defaultCreator + else: + self._defaultCreator = lambda k: defaultCreator + self._inject = inject + + def __getitem__(self, key): + try: + return dict.__getitem__(self, key) + except KeyError, err: + value = self._defaultCreator(key) + if self._inject: + self[key] = value + return self[key] + else: + return value + +class UnbufferedFile: + def __init__(self, fileObj): + self._fileObj = fileObj + + def __getattr__(self, attribute): + return getattr(self._fileObj, attribute) + + def write(self, what): + self._fileObj.write(what) + self._fileObj.flush() + +def copyFile(src, dest): + debug(Event, "cp " + src + " " + dest) + srcFile = file(src) + destFile = file(dest, "w") + destFile.write(srcFile.read()) + srcFile.close() + destFile.close() + +def unique(list): + ret = [] + inList = {} + for elem in list: + if not elem in inList: + ret.append(elem) + inList[elem] = 1 + return ret + +def unorderedEqual(listOne, listTwo, predicate = lambda x, y: x == y): + if len(listOne) != len(listTwo): + return 0 + list1 = list(listOne) + list2 = list(listTwo) + while list1 != []: + found = 0 + for index in range(len(list2)): + if predicate(list1[0], list2[index]): + del list1[0] + del list2[index] + found = 1 + break + if not found: + return 0 + return 1 + +def reduceSequences(tupleSeq, oper = operator.add): + """Return a list, where the item at position i is the reduced value + of all items at position i.""" + + if len(tupleSeq) == 0: + return [] + return [reduce(oper, [s[i] for s in tupleSeq]) + for i in range(len(tupleSeq[0]))] + +def listRemove(theList, predicate): + """Remove all items that match predicate in theList.""" + if not callable(predicate): + return listRemove(theList, lambda x: x == predicate) + else: + index = 0 + while index < len(theList): + if predicate(theList[index]): + del theList[index] + else: + index += 1 + return theList + +def logn(n, x): + return math.log(x) / math.log(n) + +def longRange(* args): + """As range, but works with long integers.""" + if len(args) == 1: + start = 0L + stop = args[0] + step = 1L + elif len(args) == 2: + (start, stop) = args + step = 1L + else: + (start, stop, step) = args + if step == 0: + raise ValueError("longRange() arg 3 must not be zero") + ret = [] + while cmp(start, stop) == cmp(0, step): + ret.append(start) + start += step + return ret + +integerSuffixes = ("", "K", "M", "G", "T") + +def abbreviateInteger(i, factor = 1024, minimise = 0): + suf = 0 + while suf < len(integerSuffixes) - 1 and \ + ((i > 9999) or (minimise and (i != 0) and (i % factor == 0))): + suf += 1 + i /= factor + return str(i) + integerSuffixes[suf] + +def readSuffixedInt(intString, factor = 1024): + if intString[-1].isalpha(): + return int(intString[: -1]) * ( + factor ** list(integerSuffixes).index(intString[-1].upper())) + else: + return int(intString) + +def normaliseIndex(index, seq): + """Return a non-negative index relating to a sequence, or negative if + index < - len(seq).""" + + if index < 0: + return len(seq) + index + else: + return index + +def truncateIndex(index, seq): + """Return a non-negative index relating to a sequence, less than or equal + to the length of the sequence.""" + ret = normaliseIndex(index, seq) + if ret < 0: + return 0 + length = len(seq) + if ret > length: + return length + return ret + +class LazySlice: + """Representation of slice into other sequence.""" + def __init__(self, seq, start = None, stop = None, step = None): + self._sequence = seq + if step is None: + self._step = 1 + else: + self._step = step + if self._step == 0: + # Provoke ValueError + [][0:1:0] + + if self._step > 0: + startDefault = 0 + stopDefault = len(seq) + indexShift = 0 + else: + startDefault = len(seq) - 1 + stopDefault = -1 + indexShift = 1 + + if start is None: + self._start = startDefault + else: + self._start = truncateIndex(start + indexShift, seq) - indexShift + if stop is None: + self._stop = stopDefault + else: + self._stop = truncateIndex(stop + indexShift, seq) - indexShift + + if (self._step > 0) != (self._start < self._stop): + self._stop = self._start + + def __getitem__(self, key): + # debug(Debug2, "LS getitem " + str(key) + ": " + repr(self)) + if type(key) == types.SliceType: + ret = LazySlice(self, key.start, key.stop, key.step) + #debug(Debug2, "LS getitem " + str(key) + " = " + + # repr(ret) + ", LS: " + repr(self)) + return ret + index = normaliseIndex(key, self) + if index < 0: + # Provoke IndexError + [][1] + seqIndex = self._start + index * self._step + if self._step > 0: + if seqIndex >= self._stop: + [][1] + elif seqIndex <= self._stop: + [][1] + ret = self._sequence[seqIndex] + #debug(Debug2, "LS getitem " + str(key) + " (" + str(seqIndex) + ") = " + + # str(ret) + ", LS: " + repr(self)) + return ret + + def __len__(self): + return (abs(self._stop - self._start) + abs(self._step) - 1) / \ + abs(self._step) + + def __str__(self): + return str(list(self)) + + def __repr__(self): + return "LazySlice(start = " + str(self._start) + ", stop = " + \ + str(self._stop) + ", step = " + str(self._step) + ",\n " + \ + repr(self._sequence) + ")" + +def getItem(sequence, key): + """Obtain key in sequence. Key may be an integer or a slice object.""" + if type(key) == types.SliceType: + if key.step is None: + return sequence[key.start : key.stop] + return sequence[key.start : key.stop : key.step] + return sequence[key] + +def binarySearch(list, value, start = 0, end = None): + "Return the position where value should be inserted in a sorted list." + if end is None: + end = len(list) + if start == end: + return end + middle = start + (end - start) / 2 + if list[middle] < value: + return binarySearch(list, value, middle + 1, end) + else: + return binarySearch(list, value, start, middle) + +def binaryFind(list, value, *args, ** kwArgs): + "Return position of value in sorted list, or None if not found." + position = binarySearch(list, value, *args, ** kwArgs) + if (position == len(list)) or (list[position] != value): + return None + else: + return position + +def compactIntListRepr(input, sort = 0): + if sort: + list = input[:] + list.sort() + else: + list = input + if __debug__: + tmp = list[:] + tmp.sort() + assert(tmp == list) + index = 0 + ret = "[" + sep = "" + compact = lambda l, i: (i + 1 < len(l)) and (l[i] + 1 == l[i + 1]) + while index < len(list): + ret += sep + str(list[index]) + if compact(list, index): + ret += "-" + while compact(list, index): + index += 1 + ret += str(list[index]) + index += 1 + sep = "," + return ret + "]" + +def predicatedIndex(sequence, predicate): + for i in range(len(sequence)): + if predicate(sequence[i]): + return i + raise ValueError() + +class ArgumentBinder: + def __init__(self, function, argument, position = 0): + self.__function = function + self.__argument = argument + self.__position = position + + def __call__(self, *args, **keywords): + newArgs = list(args) + newArgs[self.__position : self.__position] = [self.__argument] + return self.__function(*newArgs, **keywords) + +class ArgumentTupleBinder: + def __init__(self, function, arguments): + self._function = function + self._arguments = arguments + + def __call__(self, * args, ** kwArgs): + return self._function(* (self._arguments + args), ** kwArgs) + +class KeywordArgumentBinder: + def __init__(self, function, ** keywords): + self._function = function + self._keywords = keywords + + def __call__(self, * args, ** kwArgs): + kw = self._keywords + kw.update(kwArgs) + return self._function(* args, ** kw) + +cppKeywords = ["asm", "do", "inline", "short", "typeid", "auto", + "double", "int", "signed", "typename", "bool", + "dynamic_cast", "long", "sizeof", "union", "break", + "else", "mutable", "static", "unsigned", "case", + "enum", "namespace", "static_cast", "using", "catch", + "explicit", "new", "struct", "virtual", "char", + "extern", "operator", "switch", "void", "class", + "false", "private", "template", "volatile", "const", + "float", "protected", "this", "wchar_t", "const_cast", + "for", "public", "throw", "while", "continue", + "friend", "register", "true", "default", "goto", + "reinterpret_cast", "try", "delete", "if", "return", + "typedef"] + +def isCWord(str): + return not re.match("^[a-zA-Z_][a-zA-Z0-9_]*$", str) is None + +def isCppIdentifier(str): + return isCWord(str) and (str not in cppKeywords) + +def find(seq, predicate = lambda x: x): + for item in seq: + if predicate(item): + return item + return None + +def allBest(seq, comparator = cmp): + if len(seq) < 2: + return seq + seq = seq[:] + ret = [seq.pop()] + for item in seq: + c = comparator(item, ret[0]) + if c < 0: + ret = [item] + elif c == 0: + ret.append(item) + return ret + +def readFile(fileName): + f = open(fileName) + try: + ret = f.read() + return ret + finally: + f.close() + +def writeFile(fileName, contents): + f = open(fileName, "w") + try: + f.write(contents) + finally: + f.close() + +def writeFileAtomic(fileName, contents, suffix = ".tmp"): + tmpFile = fileName + suffix + writeFile(tmpFile, contents) + try: + os.rename(tmpFile, fileName) + except: + if os.path.exists(fileName) and os.path.exists(tmpFile): + os.remove(tmpFile) + raise + + +class LineIndexed: + """Read-only file object indexed by line number. The file object must + support seeking.""" + + def __init__(self, fileName, offset = 0): + if type(fileName) == type(""): + self._file = file(fileName) + else: + self._file = fileName + self._offset = offset + self._length = None + self._lineOffsets = [0] + + def close(self): + self._file.close() + + def __del__(self): + self.close() + + def _seekForward(self, index): + if self._length == 0: + raise IndexError() + for index in range(len(self._lineOffsets), index + 1): + if self._lineOffsets[-1] == self._length: + raise IndexError() + self._file.seek(self._lineOffsets[-1] + self._offset) + line = self._file.readline() + if line == "": + self._length = self._lineOffsets[-1] + else: + self._lineOffsets.append(self._lineOffsets[-1] + len(line)) + + def __getitem__(self, key): + if type(key) == types.SliceType: + ret = LazySlice(self, key.start, key.stop, key.step) + return ret + if key < 0: + positiveLength = len(self) + key + if positiveLength < 0: + raise IndexError() + return self[positiveLength] + self._seekForward(key) + offset = self._lineOffsets[key] + if offset == self._length: + raise IndexError() + self._file.seek(offset + self._offset) + return self._file.readline() + + def __len__(self): + if self._length == 0: + return 0 + while self._lineOffsets[-1] != self._length: + self._seekForward(len(self._lineOffsets)) + return len(self._lineOffsets) - 1 + + def lineOffset(self, index): + self._seekForward(index) + return self._lineOffsets[index] + + def __str__(self): + return str(list(self)) + + def __repr__(self): + return "LineIndexed(offset = " + str(self._offset) + ", length = " + \ + str(self._length) + ", lineOffsets: " + \ + str(self._lineOffsets) + ")" + +def getLine(fileName, count): + fileObj = file(fileName) + if count < 0: + return fileObj.readlines()[count] + for i in range(count + 1): + ret = fileObj.readline() + if ret == "": + raise IndexError() + return ret + +def conditionalJoin(list, separator = " ", predicate = lambda e: e != ""): + if len(list) == 0: + return "" + rest = conditionalJoin(list[1:], separator, predicate) + if not predicate(rest): + return list[0] + if not predicate(list[0]): + return rest + else: + return list[0] + separator + rest + +def pathComponents(path): + (head, tail) = os.path.split(path) + if tail == "": + if head == "": + return [] + if head == "/": + return ["/"] + else: + return pathComponents(head) + else: + return pathComponents(head) + [tail] + +def commonFirstElements(listOfLists): + if len(listOfLists) == 0: + return [] + if len(listOfLists) == 1: + return listOfLists[0] + if (len(listOfLists[0]) == 0) or (len(listOfLists[1]) == 0) or \ + (listOfLists[0][0] != listOfLists[1][0]): + return [] + else: + return commonFirstElements([ + [listOfLists[0][0]] + + commonFirstElements([listOfLists[0][1:], listOfLists[1][1:]])] + + listOfLists[2:]) + +def commonPrefix(pathList): + """Similar to os.path.commonprefix, but works on path components instead + of individual characters.""" + + assert(not isinstance(pathList, str)) + components = commonFirstElements(map(pathComponents, pathList)) + if components == []: + return "" + else: + return os.path.join(* components) + +def pathIsBelow(path, directory): + """Check if path lies below directory.""" + return commonPrefix([path, directory]) == directory + +def realPath(path): + """Similar to os.path.realpath, but handles amd gracefully.""" + ret = os.path.realpath(path) + for binDir in os.environ["PATH"].split(":") + ["/usr/sbin", "/sbin"]: + amq = os.path.join(binDir, "amq") + if os.path.isfile(amq): + try: + output = chakana.command.output(amq) + except chakana.error.CommandFailed, err: + debug(Debug, str(err)) + # Assume amd is not running + return ret + for line in output.splitlines(): + amdDir = line.split()[0] + mountDir = line.split()[-1] + if mountDir[0] == "/": + match = re.match('^(' + re.escape(line) + ')(/$)', ret) + if match: + return amdDir + ret[len(line) :] + return ret + return ret + +def removeTree(path, ignoreErrors = False): + debug(Debug, "rm -rf " + path) + if os.path.isdir(path): + shutil.rmtree(path, ignore_errors = ignoreErrors) + +def makeSymlink(value, dest, force = True, debugLevel = MinorEvent, + dryRun = False): + if os.path.islink(dest): + if os.readlink(dest) == value: + debug(Debug, "Link " + dest + " already points to " + value) + return + elif force: + debug(debugLevel, "Removing " + dest) + if not dryRun: + os.remove(dest) + else: + raise OSError((errno.EEXIST, "Link already exists", dest)) + absValue = os.path.join(os.path.dirname(dest), value) + if (not dryRun) and (not force) and (not os.path.isfile(absValue)): + raise OSError((errno.ENOENT, "Link destination does not exist", absDest)) + debug(debugLevel, "Linking " + dest + " to " + value) + if not dryRun: + os.symlink(value, dest) + +def copyTree(src, dst, symlinks = False, predicate = lambda path: 1, + preserveTimes = False): + """Similar to shutil.copytree, but allows existing destination and + passes exceptions.""" + names = filter(predicate, os.listdir(src)) + if not os.path.isdir(dst): + os.mkdir(dst) + for name in names: + srcname = os.path.join(src, name) + dstname = os.path.join(dst, name) + try: + if symlinks and os.path.islink(srcname): + linkto = os.readlink(srcname) + os.symlink(linkto, dstname) + elif os.path.isdir(srcname): + copyTree(srcname, dstname, symlinks, predicate, preserveTimes) + else: + if preserveTimes: + shutil.copy2(srcname, dstname) + else: + shutil.copy(srcname, dstname) + # XXX What about devices, sockets etc.? + except (IOError, os.error), why: + debug(Error, "Can't copy %s to %s: %s" % + (`srcname`, `dstname`, str(why))) + raise + +def makeDirsSafe(dir, mode = 0777): + """Similar to os.makedirs, but does not fail if another process is making + the directory simultaneously.""" + while not os.path.isdir(dir): + try: + os.makedirs(dir, mode) + except OSError, err: + if err.errno != errno.EEXIST: + raise + +def typeAssert(variable, type): + assert(isinstance(variable, type)) + +def intervalsOverlap(range1, range2): + if range1 > range2: + return intervalsOverlap(range2, range1) + if (range1[0] == range1[1]) or (range2[0] == range2[1]): + return 0 + return range2[0] < range1[1] + +class LexicalDistance: + def __init__(self, caseChange = 1, whiteSpaceChange = 2, + whiteSpaceRemoval = 3, ampersand = 5, other = 10, + whiteSpace = " _-'\""): + self.__caseChange = caseChange + self.__whiteSpaceChange = whiteSpaceChange + self.__whiteSpaceRemoval = whiteSpaceRemoval + self.__ampersand = ampersand + self.__other = other + self.__whiteSpace = whiteSpace + + def removeWhiteSpace(self, str1, str2, limit): + if str1[0] in self.__whiteSpace: + if limit > self.__whiteSpaceRemoval: + return self(str1[1:], str2, limit - self.__whiteSpaceRemoval) + \ + self.__whiteSpaceRemoval + return limit + + def changeWhiteSpace(self, str1, str2, limit): + if (str1[0] in self.__whiteSpace) and (str2[0] in self.__whiteSpace): + if limit > self.__whiteSpaceChange: + return self(str1[1:], str2[1:], limit - self.__whiteSpaceChange) + \ + self.__whiteSpaceChange + return limit + + def changeCase(self, str1, str2, limit): + if str1[0].upper() == str2[0].upper(): + if limit > self.__caseChange: + return self(str1[1:], str2[1:], limit - self.__caseChange) + \ + self.__caseChange + return limit + + def changeAmpersand(self, str1, str2, limit): + if (str1[0] == "&") and (str2[:3].lower() == "and"): + if limit > self.__ampersand: + return self(str1[1:], str2[3:], limit - self.__ampersand) + \ + self.__ampersand + return limit + + def removeOther(self, str1, str2, limit): + if limit > self.__other: + return self(str1[1:], str2, limit - self.__other) + self.__other + return limit + + def changeOther(self, str1, str2, limit): + if limit > self.__other: + return self(str1[1:], str2[1:], limit - self.__other) + self.__other + return limit + + def __call__(self, str1, str2, limit = 50): + ret = limit + if str1 == "": + if str2 == "": + return 0 + ret = self.removeWhiteSpace(str2, str1, ret) + ret = self.removeOther(str2, str1, ret) + elif str2 == "": + ret = self(str2, str1, ret) + else: + if str1[0] == str2[0]: + ret = self(str1[1:], str2[1:], ret) + else: + ret = self.removeWhiteSpace(str1, str2, ret) + ret = self.removeWhiteSpace(str2, str1, ret) + ret = self.changeWhiteSpace(str1, str2, ret) + ret = self.changeCase(str1, str2, ret) + ret = self.changeAmpersand(str1, str2, ret) + ret = self.changeAmpersand(str2, str1, ret) + ret = self.changeOther(str1, str2, ret) + ret = self.removeOther(str1, str2, ret) + ret = self.removeOther(str2, str1, ret) + assert(ret <= limit) + return ret + +class TryOperation: + def __init__(self, operation, description = None): + self.__operation = operation + if description is None: + self.__description = str(operation) + else: + self.__description = description + + def __call__(self, * args, ** kwargs): + try: + self.__operation(* args, ** kwargs) + except KeyboardInterrupt: + raise + except: + import debug as d + d.debug(d.Error, self.__description + " failed\n" + + d.exceptionDump(sys.exc_info())) + +def lineDirective(): + fileInfo = traceback.extract_stack(None, 2)[0] + return "#line " + repr(fileInfo[1] + 1) + " \"" + fileInfo[0] + "\"" + +try: + mkstemp = tempfile.mkstemp +except AttributeError: + def mkstemp(suffix = "", prefix = None, dir = None, text = 0): + if prefix is None: + prefix = tempfile.gettempprefix() + fileName = tempfile.mktemp(suffix) + if dir is None: + dir = os.path.dirname(fileName) + newFileName = os.path.join(dir, prefix + os.path.basename(fileName)) + if text: + flags = "w" + else: + flags = "wb" + return (file(newFileName, flags), newFileName) +