an early version of chakana, an automated test framework used with COOJA
This commit is contained in:
parent
9be473e4b9
commit
65533c0c00
29 changed files with 4983 additions and 0 deletions
13
tools/chakana/CYGWIN_USERS.txt
Normal file
13
tools/chakana/CYGWIN_USERS.txt
Normal 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
|
1
tools/chakana/__init__.py
Normal file
1
tools/chakana/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
"""Chakana, an automated test harness for networked embedded systems."""
|
30
tools/chakana/build.xml
Normal file
30
tools/chakana/build.xml
Normal 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
190
tools/chakana/command.py
Normal 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()
|
||||||
|
|
1
tools/chakana/cooja.chakana.properties
Normal file
1
tools/chakana/cooja.chakana.properties
Normal file
|
@ -0,0 +1 @@
|
||||||
|
DEFAULT_PROJECTDIRS=../cooja_plugin
|
3
tools/chakana/cooja_plugin/README
Normal file
3
tools/chakana/cooja_plugin/README
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
Main Chakana COOJA plugin.
|
||||||
|
|
||||||
|
Communicates with Chakana, and should automatically be started with the simulator.
|
40
tools/chakana/cooja_plugin/build.xml
Normal file
40
tools/chakana/cooja_plugin/build.xml
Normal 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>
|
2
tools/chakana/cooja_plugin/cooja.config
Normal file
2
tools/chakana/cooja_plugin/cooja.config
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
se.sics.cooja.GUI.PLUGINS = + se.sics.chakana.ChakanaPlugin
|
||||||
|
se.sics.cooja.GUI.JARFILES = + chakana.jar
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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
|
@ -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();
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
279
tools/chakana/debug.py
Normal 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
89
tools/chakana/error.py
Normal 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
175
tools/chakana/event.py
Normal 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
126
tools/chakana/harness.py
Normal 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
268
tools/chakana/linux.py
Normal 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
184
tools/chakana/monitor.py
Normal 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
344
tools/chakana/shepherd.py
Normal 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
287
tools/chakana/threads.py
Normal 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
736
tools/chakana/utils.py
Normal 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)
|
||||||
|
|
Loading…
Reference in a new issue