an early version of chakana, an automated test framework used with COOJA

This commit is contained in:
fros4943 2007-08-21 14:39:18 +00:00
parent 9be473e4b9
commit 65533c0c00
29 changed files with 4983 additions and 0 deletions

View file

@ -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 <fros@sics.se>, 2007

View file

@ -0,0 +1 @@
"""Chakana, an automated test harness for networked embedded systems."""

30
tools/chakana/build.xml Normal file
View file

@ -0,0 +1,30 @@
<?xml version="1.0"?>
<project name="Chakana COOJA" default="compile_plugin" basedir=".">
<property name="contiki" value="../.."/>
<property name="cooja" value="${contiki}/tools/cooja"/>
<property name="cooja_jar" value="${cooja}/dist/cooja.jar"/>
<property name="build" location="build"/>
<property name="tools_config" value="cooja.chakana.properties"/>
<target name="init">
<tstamp/>
</target>
<target name="compile_cooja" depends="init">
<ant antfile="build.xml" dir="${cooja}" target="jar" inheritAll="false"/>
</target>
<target name="compile_plugin" depends="init, compile_cooja">
<ant antfile="build.xml" dir="cooja_plugin" target="jar" inheritAll="false">
<property name="contiki" value="${contiki}/.."/>
</ant>
</target>
<target name="clean" depends="init">
<delete dir="${build}"/>
</target>
</project>

190
tools/chakana/command.py Normal file
View file

@ -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 <lalle@sics.se>.
#
# $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()

View file

@ -0,0 +1 @@
DEFAULT_PROJECTDIRS=../cooja_plugin

View file

@ -0,0 +1,3 @@
Main Chakana COOJA plugin.
Communicates with Chakana, and should automatically be started with the simulator.

View file

@ -0,0 +1,40 @@
<?xml version="1.0"?>
<project name="Chakana COOJA plugin" default="jar" basedir=".">
<property name="source" location="java"/>
<property name="build" location="build"/>
<property name="lib" location="lib"/>
<property name="contiki" location="../../.."/>
<property name="cooja" location="${contiki}/tools/cooja"/>
<property name="cooja_jar" value="${cooja}/dist/cooja.jar"/>
<target name="init">
<tstamp/>
</target>
<target name="compile" depends="init">
<mkdir dir="${build}"/>
<javac srcdir="${source}" destdir="${build}">
<classpath>
<pathelement path="."/>
<pathelement location="${cooja_jar}"/>
</classpath>
</javac>
</target>
<target name="clean" depends="init">
<delete dir="${build}"/>
</target>
<target name="jar" depends="init, compile">
<mkdir dir="${lib}"/>
<jar destfile="${lib}/chakana.jar" basedir="${build}">
<fileset dir="${build}"/>
<manifest>
<attribute name="Class-Path" value="."/>
</manifest>
</jar>
</target>
</project>

View file

@ -0,0 +1,2 @@
se.sics.cooja.GUI.PLUGINS = + se.sics.chakana.ChakanaPlugin
se.sics.cooja.GUI.JARFILES = + chakana.jar

View file

@ -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<Element> getConfigXML() {
return null;
}
public boolean setConfigXML(Collection<Element> configXML,
boolean visAvailable) {
return false;
}
}

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

@ -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);
}
}

View file

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

View file

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

View file

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

View file

@ -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 {
}

View file

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

View file

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

View file

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

279
tools/chakana/debug.py Normal file
View file

@ -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])

89
tools/chakana/error.py Normal file
View file

@ -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)

175
tools/chakana/event.py Normal file
View file

@ -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)
])

126
tools/chakana/harness.py Normal file
View file

@ -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

268
tools/chakana/linux.py Normal file
View file

@ -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

184
tools/chakana/monitor.py Normal file
View file

@ -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

344
tools/chakana/shepherd.py Normal file
View file

@ -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 = ['<command value="' + name + '">']
for (key, value) in args.items():
if value is None:
command.append('<' + key + '/>')
else:
command.append('<' + key + '>' + str(value) + '</' + key + '>')
command.append(xmlContent + '</command>')
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("</" + re.escape(rootElement) + ">$", 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),
])

287
tools/chakana/threads.py Normal file
View file

@ -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 <lalle@sics.se>.
#
# $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)

736
tools/chakana/utils.py Normal file
View file

@ -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)