git-svn-id: https://svn.apache.org/repos/asf/activemq/trunk@565392 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Robert Davies 2007-08-13 16:03:25 +00:00
parent 2edbcd6298
commit 09f8ac5542
45 changed files with 2061 additions and 0 deletions

14
log_analyzer_tool/Main.py Executable file
View File

@ -0,0 +1,14 @@
"""
Module Main
"""
from loganalyzergui.Application import Application
def main():
"""
Entrance point for the application
"""
app = Application(0)
app.MainLoop()
if __name__ == '__main__':
main()

64
log_analyzer_tool/README.txt Executable file
View File

@ -0,0 +1,64 @@
Readme file for the LogAnalyzer application.
1. Requirements:
-Python 2.5.1 (http://www.python.org/download/releases/2.5.1/ or your favorite package)
-wxPython 2.8.4 (http://www.wxpython.org/download.php or your favorite package)
2. How to execute:
Run 'python Main.py'.
3. Some instructions:
-This tool will analyze ActiveMQ log files that have been produced
using the 'custom' transport log format. To analyze the files,
put them in a directory, choose that directory and click 'Parse'.
Please don't put any other kind of files in the same directory
(sub-directories won't cause any problem, but the files inside
them will not be analyzed).
For example, imagine you have a setup with 4 machines: 1 has producers,
2 are brokers, and 1 has consumers. As long as you have 1 JVM per machine,
you should have 4 log files. Call the files p.log, b1.log, b2.log,
and c.log, for example. Put the 4 files in the same directory,
choose that directory and click the 'Parse' button.
-The first tab of the tool shows incorrect situations at transport level:
(i) Messages that were sent through a connection, but were not received
at the other end.
(ii) Messages that were received through a connection, but were not sent
(probably you are missing the log file of the JVM that sent the message).
(iii) Messages that are sent 2 times through the same connection.
(iv) Messages that were sent 2 times by the same JVM, but through
different connections.
By clicking the 'Show results with short ids' checkbox, you can switch
between the real connection / producer id used by ActiveMQ,
or a unique integer assigned by the tool.
Often it's easier to compare and browse with this integers than with
the original id's which are often long strings.
The 'Message id' column shows 2 things: the id of the producer that
originally issued the message, and the 'Producer Sequence Id' of a message.
These 2 items identify a message in a unique way.
You can use the checkboxes to filter per type.
You can also filter by a given connection (but then problems of type (iv)
will not appear because they 'belong' to more than one connection).
You can input a 'long id' (the original ActiveMQ id) or a 'short id'
(a short integer assigned by the tool to each connection).
-The second tab of the tool allows you to get a lot of information
about a single message. Input the producer id of the original producer
of the message, and the message's 'Producer Sequence Id'.
You can choose to use the original ActiveMQ producer id (long id)
or the short integer assigned to a producer by the tool.
You can also use the 'Jump to Message Browsing' button of the 1st tab
to see the information about the problems of a given message
without having to copy the message id manually.
In this tab you can also use the 'Show results with short ids' checkbox.
-The third tab gives a summary of the clients (producer and consumers)
which appear in the log files. Each client is identified by a short id,
and belongs to a connection (whose 'short id' and 'long id' are shown).
-The fourth tab gives a summary of the connections involved,
and the clients that belong to them.
-The fifth tab gives a summary of the log files analyzed,
and the connections in each of the files.

View File

@ -0,0 +1,337 @@
"""
Module Connection
"""
import itertools
class Connection(object):
"""
This class represents an ActiveMQ Connection.
It also stores a collection of the connections
that have been read in the log files.
A Connection's id is the ActiveMQConnection's id. Since this is usually a long
alphanumerical string, it is called 'longId'.
Each new Connection gets also assigned an integer 'shortId' automatically.
The function of this 'shortId' is to make understanding of displayed data easier.
A Connection has 2 LogFile members, who represent:
-the log file of the JVM that initiates a connection.
-the log file of the JVM that receives a connection request.
The purpose of every Connection is to store the following data:
-messages sent through this connection, as a dictionary where the
key is a tuple (message, direction) and the value is
a list of timestamps. If the message was sent only one time (normal case),
the timestamp list will have 1 item only.
-messages received through this connection, as a dictionary
analogous to the previous one.
-messages sent but not received through this connection, as a list of
tuples (storedMessage, ntimes, timestamps).
'storedMessage' is a (message, direction) tuple
ntimes, an integer, is the number of times a message was sent but not received
timestamps is a list of timestamps of when the message was sent or received.
For a message to be in this list, ntimes must be >= 1.
-messages received but not sent through this connection.
Analog to previous point.
-messages sent more than 2 more times through this connection, as a list of
tuples (storedMessage, ntimes, timestamps).
'storedMessage' is a (message, direction) tuple
ntimes, an integer, is the number of times a message was sent.
timestamps is a list of timestamps of when the message was sent.
For a message to be in this list, ntimes must be >= 2.
-messages received more than 2 more times through this connection.
Identical structure to the previous point.
The 'direction' value is either True or False.
True represents that the message was sent from the JVM writing to the
'from' file, to the JVM writing to the 'to' file.
False represents the opposite.
"""
#dictionary whose keys are connection ids, and whose values
#are Connection objects
connections = {}
nConnections = 0
connectionIdList = []
def __init__(self, longId, fromFile = None, toFile = None):
"""
Constructs a Connection object.
longId : string
fromFile: LogFile object
to: LogFile object
The ActiveMQConnection's id has to be provided.
Optionally, 2 LogFile objects can be provided.
The 'from' file is the log file of the JVM that initiates a connection.
The 'to' file is the log file of the JVM that receives a connection request.
A new connection gets automatically a new 'shortId', which is an integer.
The longId gets also stored in a list of longIds.
Returns a Connection object.
"""
self.longId = longId
self.fromFile = fromFile
self.toFile = toFile
self.shortId = Connection.nConnections
Connection.connectionIdList.append(longId)
Connection.nConnections += 1
self.producers = set()
self.consumers = set()
self.sent = {}
self.received = {}
self.duplicateSent = None
self.duplicateReceived = None
self.sentButNotReceived = None
self.receivedButNotSent = None
self.calculated = False
@classmethod
def clearData(cls):
"""
Deletes all information read about connections.
Returns nothing.
"""
cls.connections.clear()
cls.nConnections = 0
del cls.connectionIdList[:]
@classmethod
def getConnectionByLongId(cls, longId):
"""
Retrieves the connection whose id is 'longId'.
If there is no connection with this id, a new
one is created with this id.
Returns a Connection object.
"""
if longId not in cls.connections:
cls.connections[longId] = Connection(longId)
return cls.connections[longId]
@classmethod
def getConnectionByShortId(cls, shortId):
"""
Retrieves the connection whose shortId is 'shortId'.
If there is no connection with this id,
an IndexError exception will be thrown.
Returns a Connection object.
Throws an IndexError if the short id does not exist.
"""
return cls.connections[cls.connectionIdList[shortId]]
@classmethod
def shortIdToLongId(cls, shortId):
"""
Transforms a connection's short id to a long id.
Returns the long id.
Throws an IndexError if the short id does not exist.
"""
return cls.connectionIdList[shortId]
@classmethod
def longIdToShortId(cls, longId):
"""
Transforms a connection's long id to a short id.
Returns the short id.
Throws an KeyError if the short id does not exist.
"""
try:
return cls.connections[longId].shortId
except KeyError:
print longId
print cls.connections
raise
@classmethod
def setFrom(cls, longId, fromFile):
"""
Sets the 'from' LogFile object for the connection whose id is 'longId'.
The 'from' file is the log file of the JVM that initiates a connection.
If there is not yet a connection whose id is 'longId', a new one is
created with this longId and this 'from' file.
Returns nothing.
"""
if longId not in cls.connections:
cls.connections[longId] = Connection(longId, fromFile = fromFile)
else:
cls.connections[longId].fromFile = fromFile
@classmethod
def setTo(cls, longId, toFile):
"""
Sets the 'to' LogFile object for the connection whose id is 'longId'.
The 'to' file is the log file of the JVM that receives a connection request.
If there is not yet a connection whose id is 'longId', a new one is
created with this longId and this 'to' file.
Returns nothing.
"""
if longId not in cls.connections:
cls.connections[longId] = Connection(longId, toFile = toFile)
else:
cls.connections[longId].toFile = toFile
@classmethod
def exists(cls, longId):
"""
Returns if there is a connection whose id is 'longId'
"""
return longId in cls.connections
def addProducer(self, producer):
"""
Adds a producer to the set of this connection's producers.
Returns nothing.
"""
self.producers.add(producer)
def addConsumer(self, consumer):
"""
Adds a consumer to the set of this connection's consumers.
Returns nothing.
"""
self.consumers.add(consumer)
def addSentMessage(self, message, direction, timestamp):
"""
Adds a message to the set of messages sent through this connection.
message: a Message object
direction: True if this message was sent from self.fromFile to self.to
False if this message was sent from self.toFile to self.fromFile
timestamp: a string with the time this message was sent
If the message has already been sent in this direction, it gets added to the
collection of duplicate sent messages.
Returns nothing.
"""
storedMessage = (message, direction)
if storedMessage in self.sent:
self.sent[storedMessage].append(timestamp)
else:
self.sent[storedMessage] = [timestamp]
def addReceivedMessage(self, message, direction, timestamp):
"""
Adds a message to the set of messages received through this connection.
message: a message object
direction: True if this message was sent from self.fromFile to self.to
False if this message was sent from self.toFile to self.fromFile
timestamp: a string with the time this message was sent
If the message has already been received in this direction, it gets added to the
collection of duplicate received messages.
Returns nothing.
"""
storedMessage = (message, direction)
if storedMessage in self.received:
self.received[storedMessage].append(timestamp)
else:
self.received[storedMessage] = [timestamp]
def getErrors(self):
"""
Processes the data previously gathered to find incorrect situations.
Returns a 4-tuple with:
-collection of sent but not received messages, through this Connection.
This collection is a list of (storedMessage, ntimes, timestamps) tuples where:
*'storedMessage' is a (message, direction) tuple.
*'ntimes' is an integer, representing how many times the message was sent but not received.
*'timestamps' is a list of strings with the timestamps when this message was sent / received.
-collection of received but not sent messages, through this Connection.
This collection is a list of (storedMessage, ntimes, timestamps) tuples where:
*'storedMessage' is a (message, direction) tuple.
*'ntimes' is an integer, representing how many times the message was received but not sent.
*'timestamps' is a list of strings with the timestamps when this message was sent / received.
-collection of duplicate sent messages, through this Connection.
This collection is a list of (message, timestamps) tuples where:
*'storedMessage' is a (shortId, commandId, direction) tuple.
*'ntimes' is an integer, representing how many times the message sent.
*'timestamps' is a list of strings with the timestamps when this message was sent.
-collection of duplicate received messages, through this Connection.
This collection is a list of (message, timestamps) tuples where:
*'storedMessage' is a (message, direction) tuple.
*'ntimes' is an integer, representing how many times the message received.
*'timestamps' is a list of strings with the timestamps when this message was received.
The data is only calculated once, and then successive calls of this method return always
the same erros unles self.calculated is set to False.
"""
if not self.calculated:
self.sentButNotReceived = []
for message, timestamps in self.sent.iteritems():
if message not in self.received:
self.sentButNotReceived.append((message, len(timestamps), timestamps))
else:
difference = len(timestamps) - len(self.received[message])
if difference > 0:
self.sentButNotReceived.append((message, difference,
itertools.chain(timestamps, self.received[message])))
self.receivedButNotSent = []
for message, timestamps in self.received.iteritems():
if message not in self.sent:
self.receivedButNotSent.append((message, len(timestamps), timestamps))
else:
difference = len(timestamps) - len(self.sent[message])
if difference > 0:
self.receivedButNotSent.append((message, difference,
itertools.chain(timestamps, self.sent[message])))
self.duplicateSent = [(message, len(timestamps), timestamps)
for message, timestamps in self.sent.iteritems() if len(timestamps) > 1]
self.duplicateReceived = [(message, len(timestamps), timestamps)
for message, timestamps in self.received.iteritems() if len(timestamps) > 1]
self.sentButNotReceived.sort(key = lambda message: (message[0][0].producer.shortId, message[0][0].prodSeqId))
self.receivedButNotSent.sort(key = lambda message: (message[0][0].producer.shortId, message[0][0].prodSeqId))
self.duplicateSent.sort(key = lambda message: (message[0][0].producer.shortId, message[0][0].prodSeqId))
self.duplicateReceived.sort(key = lambda message: (message[0][0].producer.shortId, message[0][0].prodSeqId))
self.calculated = True
return self.sentButNotReceived, self.receivedButNotSent, self.duplicateSent, self.duplicateReceived
def __str__(self):
"""
Represents this Connection object as a string.
"""
return ''.join([self.longId, ' from:', str(self.fromFile), ' to:', str(self.toFile)])

Binary file not shown.

View File

@ -0,0 +1,76 @@
"""
Module Consumer
"""
class Consumer(object):
"""
This class represents an ActiveMQ Consumer.
Each consumer is identified by its long id.
However each consumer also has a short id (an integer) to identify it more easily.
"""
nConsumers = 0
consumerIdList = []
consumers = {}
def __init__(self, longId):
"""
Constructor
"""
self.longId = longId
self.shortId = Consumer.nConsumers
self.connectionId, sessionId, value = longId.rsplit(':', 2)
self.sessionId = int(sessionId)
self.value = int(value)
Consumer.consumers[longId] = self
Consumer.consumerIdList.append(self.longId)
Consumer.nConsumers += 1
@classmethod
def clearData(cls):
"""
Deletes all information read about Consumers.
Returns nothing.
"""
cls.consumers.clear()
cls.nConsumers = 0
del cls.consumerIdList[:]
@classmethod
def getConsumerByLongId(cls, longId):
"""
Returns a consumer given its long id.
If there is no consumer with this long id yet, it will be created.
"""
if longId not in cls.consumers:
cls.consumers[longId] = Consumer(longId)
return cls.consumers[longId]
@classmethod
def shortIdToLongId(cls, shortId):
"""
Transforms a consumer's short id to a long id.
Returns a long id.
Throws an IndexError if the short id does not exist.
"""
return cls.consumerIdList[shortId]
@classmethod
def longIdToShortId(cls, longId):
"""
Transforms a consumer's long id to a short id.
Returns a long id.
Throws an KeyError if the long id does not exist.
"""
return cls.consumers[longId].shortId

Binary file not shown.

View File

@ -0,0 +1,187 @@
"""
Module LogFile
"""
import os
class LogFile(object):
"""
Class that represents an ActiveMQ log file read by the application.
It also stores a list of all the LogFile objects.
A LogFile object stores the following information:
-A list of 'outgoing' Connection objects that represent the connections
created by the JVM that writes this LogFile.
-A list of 'incoming' Connection objects that represent the connections
requests received by the JVM that writes this LogFile.
-A dictionary of messages that were sent in this file.
The keys are Message objects and the values
are lists of timestamps of when the message was sent.
-A list of messages that were received in this file.
The keys are Message objects and the values
are lists of timestamps of when the message was received.
-A list of messages that were sent in this file more than 1 time (duplicates)
The list is made of (message, ntimes, timestamps) tuples.
-A list of messages that were received in this file more than 1 time (duplicates),
analogous to the previous structure.
"""
logfiles = []
def __init__(self, path):
"""
Constructs a LogFile object.
path: a string with the path to the ActiveMQ log file.
"""
self.__path = os.path.abspath(path)
self.file = open(self.__path, 'r')
self.outgoing = []
self.incoming = []
self.sent = {}
self.received = {}
self.duplicateReceived = None
self.duplicateSent = None
self.calculated = False
LogFile.logfiles.append(self)
@classmethod
def clearData(cls):
"""
Class method erases all the LogFile objects stored in the LogFile class.
Returns nothing.
"""
del cls.logfiles[:]
@classmethod
def closeFiles(cls):
"""
Class method that closes all the LogFile objects stored in the LogFile class.
Returns nothing.
"""
for logFile in cls.logfiles:
logFile.file.close()
def addOutgoingConnection(self, con):
"""
Adds an 'outgoing' Connection object to this LogFile.
Returns nothing.
"""
self.outgoing.append(con)
def addIncomingConnection(self, con):
"""
Adds an 'incoming' Connection object to this LogFile.
Returns nothing.
"""
self.incoming.append(con)
def addSentMessage(self, message, timestamp):
"""
Adds a message to the set of messages that were sent thtough this file.
If a message gets sent 2 times, it gets added to the set of duplicate sent messages.
message: a Message object.
timestamp: a string with the time where this message was sent.
Returns nothing.
"""
if message in self.sent:
self.sent[message].append(timestamp)
else:
self.sent[message] = [timestamp]
def addReceivedMessage(self, message, timestamp):
"""
Adds a message to the set of messages that were received in this file.
If a message gets sent 2 times, it gets added to the set of duplicate received messages.
message: a Message object.
timestamp: a string with the time where this message was sent.
Returns nothing.
"""
#message = (shortProdId, prodSeqId, False)
if message in self.received:
self.received[message].append(timestamp)
else:
self.received[message] = [timestamp]
def getErrors(self):
"""
Returns a 2-tuple with:
-a list of (message, ntimes, timestamps) tuples, with the duplicate sent messages
that appear in more than one connection in this file.
'message' is a Message object.
'ntimes' is an integer stating how many times the message was sent ( always >= 2)
'timestamps' is a list of timestamps with the instants the message was sent.
-a list of (message, ntimes, timestamps) tuples, with the duplicate received messages
that appear in more than one connection in this file.
Structure analogous to previous one.
The data is only calculated once, and then successive calls of this method return always
the same erros unles self.calculated is set to False.
"""
if not self.calculated:
duplicateSentTemp = [(message, len(timestamps), timestamps)
for message, timestamps in self.sent.iteritems() if len(timestamps) > 1]
self.duplicateSent = []
for message, _, timestamps in duplicateSentTemp:
connections = []
for connection, direction in message.sendingConnections:
if direction and connection.fromFile == self \
or not direction and connection.toFile == self:
connections.append(connection)
if len(connections) > 1:
self.duplicateSent.append((message, len(timestamps), timestamps))
duplicateReceivedTemp = [(message, len(timestamps), timestamps)
for message, timestamps in self.received.iteritems() if len(timestamps) > 1]
self.duplicateReceived = []
for message, _, timestamps in duplicateReceivedTemp:
connections = []
for connection, direction in message.receivingConnections:
if direction and connection.toFile == self \
or not direction and connection.fromFile == self:
connections.append(connection)
if len(connections) > 1:
self.duplicateReceived.append((message, len(timestamps), timestamps))
self.duplicateSent.sort(key = lambda message: (message[0].producer.shortId, message[0].prodSeqId))
self.duplicateReceived.sort(key = lambda message: (message[0].producer.shortId, message[0].prodSeqId))
self.calculated = True
return self.duplicateSent, self.duplicateReceived
def close(self):
"""
Closes the underlying file.
Returns nothing.
"""
self.file.close()
def __str__(self):
"""
Returns a string representation of this object.
"""
return self.__path

Binary file not shown.

View File

@ -0,0 +1,188 @@
"""
Module LogParser
"""
import os, sys, time
from LogFile import LogFile
from Connection import Connection
from Producer import Producer
from Consumer import Consumer
from Message import Message
MESSAGE_TYPES = frozenset(['ActiveMQBytesMessage', 'ActiveMQTextMessage'])
DISPATCH_MESSAGE = 'MessageDispatch'
ADVISORY_TEXT = 'Advisory'
CONSUMER_TEXT = 'toConsumer:'
class LogParser(object):
"""
This class is in charge of parsing the log files and storing the data
as Connection, LogFile and Message objects.
"""
instance = None
@classmethod
def getInstance(cls):
"""
Returns the sole instance of the class.
"""
if cls.instance is None:
cls.instance = LogParser()
return cls.instance
@classmethod
def deleteInstance(cls):
"""
Deletes the sole instance of the class
"""
cls.instance = None
def parse (self, logFile):
"""
Parses the information in a log file.
logFile should be a LogFile object.
Returns nothing.
"""
try:
for line in logFile.file:
loggedMessage = line.partition('$$ ')[2]
if loggedMessage != '':
spacedStrings = loggedMessage.split()
if spacedStrings[1] == 'ConnectionInfo':
connectionId = spacedStrings[3]
if spacedStrings[0] == 'SENDING:':
logFile.addOutgoingConnection(Connection.getConnectionByLongId(connectionId))
Connection.setFrom(connectionId, logFile)
elif spacedStrings[0] == 'RECEIVED:':
logFile.addIncomingConnection(Connection.getConnectionByLongId(connectionId))
Connection.setTo(connectionId, logFile)
else:
raise Exception('Exception: ConnectionInfo: not SENDING or RECEIVED')
elif spacedStrings[1] in MESSAGE_TYPES or spacedStrings[1] == DISPATCH_MESSAGE:
timestamp = line[0:23]
commaValues = spacedStrings[3].split(',')
messageId = commaValues[0]
producerId = messageId[:messageId.rindex(':')]
connection = Connection.getConnectionByLongId(commaValues[2]) #commaValues[2] = connectionId
producer = Producer.getProducerByLongId(producerId)
producerConnection = Connection.getConnectionByLongId(producerId.rsplit(':', 2)[0]) #producerConnectionId
message = Message.getMessage(producer,
int(messageId[messageId.rindex(':') + 1:]), #producerSequenceId
commaValues[-1] == ADVISORY_TEXT)
producerConnection.addProducer(producer)
if spacedStrings[1] in MESSAGE_TYPES:
if spacedStrings[0] == 'SENDING:':
direction = (logFile == connection.fromFile)
connection.addSentMessage(message, direction, timestamp)
logFile.addSentMessage(message, timestamp)
message.addSendingConnection(connection, direction, connection,
int(commaValues[1]), timestamp) #commaValues[1] = commandId
elif spacedStrings[0] == 'RECEIVED:':
direction = (logFile == connection.toFile)
connection.addReceivedMessage(message, direction, timestamp)
logFile.addReceivedMessage(message, timestamp)
message.addReceivingConnection(connection, direction, connection,
int(commaValues[1]), timestamp) #commaValues[1] = commandId
elif spacedStrings[1] == DISPATCH_MESSAGE:
#additional parsing to get the consumer
consumerId = spacedStrings[4][len(CONSUMER_TEXT):]
consumer = Consumer.getConsumerByLongId(consumerId)
consumerConnection = Connection.getConnectionByLongId(':'.join(consumerId.split(':')[:3]))
consumerConnection.addConsumer(consumer)
if spacedStrings[0] == 'SENDING:':
direction = (logFile == connection.fromFile)
consumerConnection.addSentMessage(message, direction, timestamp)
logFile.addSentMessage(message, timestamp)
message.addSendingConnection(consumerConnection, direction, connection,
int(commaValues[1]), timestamp) #commaValues[1] = commandId
elif spacedStrings[0] == 'RECEIVED:':
direction = (logFile == connection.toFile)
consumerConnection.addReceivedMessage(message, direction, timestamp)
logFile.addReceivedMessage(message, timestamp)
message.addReceivingConnection(consumerConnection, direction, connection,
int(commaValues[1]), timestamp) #commaValues[1] = commandId
except Exception:
print logFile, line
raise
def clearData(self):
"""
Clears all the data parsed.
"""
Connection.clearData()
Producer.clearData()
Consumer.clearData()
Message.clearData()
LogFile.clearData()
def parseDirectory(self, directory):
"""
Parses a directory of log files.
"""
self.clearData()
fileNames = os.walk(directory).next()[2]
logFiles = [LogFile(directory + os.sep + fileName) for fileName in fileNames]
for logFile in logFiles:
self.parse(logFile)
LogFile.closeFiles()
def main():
"""
Entrance point for the command line test.
"""
if len(sys.argv) != 2:
print 'Usage: python LogParser.py directory'
else:
startTime = time.time()
LogParser.getInstance().parseDirectory(sys.argv[1])
LogParser.deleteInstance()
print str(Message.messageCount) + ' messages parsed'
print 'in ' + str(time.time() - startTime) + ' seconds'
print 'press a key'
sys.stdin.read(3)
startTime = time.time()
for connection in Connection.connections.itervalues():
connection.getErrors()
for logFile in LogFile.logfiles:
logFile.getErrors()
print 'additional: ' + str(time.time() - startTime) + ' seconds'
time.sleep(36000)
if __name__ == '__main__':
main()

Binary file not shown.

View File

@ -0,0 +1,120 @@
"""
Module Message
"""
class Message(object):
"""
Objects of this class represent ActiveMQ messages.
They are used to store the 'travel path' of every message.
This class also stores a collection of all the Message objects.
"""
messages = {}
messageCount = 0
def __init__(self, producer, prodSeqId, advisory):
"""
Constructs a message object, given a producer and producer sequence id.
"""
self.producer = producer
self.prodSeqId = prodSeqId
self.advisory = advisory
self.sendingConnections = {}
self.receivingConnections = {}
Message.messageCount += 1
@classmethod
def clearData(cls):
"""
Deletes all the messages.
Returns nothing.
"""
cls.messages.clear()
cls.messageCount = 0
@classmethod
def getMessage(cls, producer, prodSeqId, advisory = False):
"""
Returns the Message object identified by (producer, prodSeqId)
where producer is a producer object and prodSeqId is a producer sequence id
message was first sent.
If the Message object does not exist, it will be created.
Returns a Message object.
"""
messageId = (producer, prodSeqId)
if messageId not in cls.messages:
cls.messages[messageId] = Message(producer, prodSeqId, advisory)
return cls.messages[messageId]
@classmethod
def exists(cls, producer, prodSeqId):
"""
Returns if there is a Message object identified by (producer, prodSeqId)
"""
return (producer, prodSeqId) in cls.messages
def addSendingConnection(self, connection, direction, mostRecentConId, commandId, timestamp):
"""
Adds a connection to the set of connections through which this message was sent.
The 'direction' argument is True if the message was sent from the file
connection.fromFile to the file connection.toFile, and False otherwise.
'timestamp' is a string with the moment this message was sent trough the connection.
Returns nothing.
"""
storedConnection = (connection, direction)
if storedConnection in self.sendingConnections:
self.sendingConnections[storedConnection].append((mostRecentConId, commandId, timestamp))
else:
self.sendingConnections[storedConnection] = [(mostRecentConId, commandId, timestamp)]
def addReceivingConnection(self, connection, direction, mostRecentConId, commandId, timestamp):
"""
Adds a connection to the set of connections where this message was received.
The 'direction' argument is True if the message was sent from the file
connection.fromFile to the file connection.toFile, and False otherwise.
'timestamp' is a string with the moment this message was received trough the connection.
Returns nothing.
"""
storedConnection = (connection, direction)
if storedConnection in self.receivingConnections:
self.receivingConnections[storedConnection].append((mostRecentConId, commandId, timestamp))
else:
self.receivingConnections[storedConnection] = [(mostRecentConId, commandId, timestamp)]
def getFiles(self):
"""
Returns a 2-tuple with the following 2 sets:
-set of LogFile objects where this message was sent.
-set of LogFile objects where this message was received.
"""
sendingFiles = set()
receivingFiles = set()
for connection, direction in self.sendingConnections:
if direction:
sendingFiles.add(connection.fromFile)
else:
sendingFiles.add(connection.toFile)
for connection, direction in self.receivingConnections:
if direction:
receivingFiles.add(connection.toFile)
else:
receivingFiles.add(connection.fromFile)
return sendingFiles, receivingFiles

Binary file not shown.

View File

@ -0,0 +1,94 @@
"""
Module Producer
"""
class Producer(object):
"""
This class represents an ActiveMQ Producer.
Each producer is identified by its long id.
However each producer also has a short id (an integer) to identify it more easily.
"""
nProducers = 0
producerIdList = []
producers = {}
def __init__(self, longId):
"""
Constructor
"""
self.longId = longId
self.shortId = Producer.nProducers
self.connectionId, sessionId, value = longId.rsplit(':', 2)
self.sessionId = int(sessionId)
self.value = int(value)
Producer.producers[longId] = self
Producer.producerIdList.append(self.longId)
Producer.nProducers += 1
@classmethod
def clearData(cls):
"""
Deletes all information read about producers.
Returns nothing.
"""
cls.producers.clear()
cls.nProducers = 0
del cls.producerIdList[:]
@classmethod
def getProducerByLongId(cls, longId):
"""
Returns a producer given its long id.
If there is no producer with this long id yet, it will be created.
"""
if longId not in cls.producers:
cls.producers[longId] = Producer(longId)
return cls.producers[longId]
@classmethod
def getProducerByShortId(cls, shortId):
"""
Returns a producer given its short id.
If there is no producer with thi short id yet, IndexError will be thrown.
"""
return cls.producers[cls.producerIdList[shortId]]
@classmethod
def exists(cls, longid):
"""
Returns if a producer with the given long id exists.
"""
return longid in cls.producers
@classmethod
def shortIdToLongId(cls, shortId):
"""
Transforms a producer's short id to a long id.
Returns a long id.
Throws an IndexError if the short id does not exist.
"""
return cls.producerIdList[shortId]
@classmethod
def longIdToShortId(cls, longId):
"""
Transforms a producer's long id to a short id.
Returns a long id.
Throws an KeyError if the long id does not exist.
"""
return cls.producers[longId].shortId

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,71 @@
"""
Module Application
"""
import wx
from DirectoryPanel import DirectoryPanel
from TabbedPanel import TabbedPanel
class MainPanel(wx.Panel):
"""
Panel contained into the window of the application.
It contains a DirectoryPanel and a TabbedPanel.
"""
def __init__(self, parent):
"""
Constructor
"""
wx.Panel.__init__(self, parent, -1)
self.tabbedPanel = TabbedPanel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(DirectoryPanel(self), 0, wx.EXPAND|wx.ALL, 5)
sizer.Add(self.tabbedPanel, 1, wx.EXPAND)
self.SetSizer(sizer)
def logDataUpdated(self):
"""
Method to be called when the parsed data has been updated.
The Panel will notify its children components.
"""
self.tabbedPanel.logDataUpdated()
class MainFrame(wx.Frame):
"""
This class represents the window of the application.
It contains a MainPanel object.
We need to add a wx.Panel to a wx.Frame to avoid
graphical problems in Windows.
"""
def __init__(self, parent):
"""
Constructor
"""
wx.Frame.__init__(self, parent, 100,
'ActiveMQ Log Analyzer Tool', size=(1024,800))
# ib = wx.IconBundle()
# ib.AddIconFromFile("logparser.ico", wx.BITMAP_TYPE_ANY)
# self.SetIcons(ib)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(MainPanel(self), 1, wx.EXPAND)
self.SetSizer(sizer)
self.Centre()
self.Show(True)
class Application(wx.App):
"""
Main class of the application
"""
def OnInit(self):
"""
To be executed when Application is launched
"""
MainFrame(None)
return True

Binary file not shown.

View File

@ -0,0 +1,61 @@
"""
Module DirectoryPanel
"""
import wx
import os
from loganalyzerengine.LogParser import LogParser
class DirectoryPanel(wx.Panel):
"""
Panel to choose the directory with the log files to be parsed,
and launch the parsing / analyzing process.
"""
def __init__(self, parent):
"""
Constructor
"""
wx.Panel.__init__(self, parent, -1)
self.mainPanel = parent
self.textctrl = wx.TextCtrl(self, -1, 'C:\logs')
self.lastDirectoryOpen = ''
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(wx.StaticText(self, -1, 'Directory'), 0, wx.CENTER|wx.RIGHT, 5)
sizer.Add(self.textctrl, 1, wx.CENTER|wx.RIGHT, 5)
sizer.Add(wx.Button(self, 100 , 'Choose'), 0, wx.CENTER|wx.RIGHT, 5)
sizer.Add(wx.Button(self, 101 , 'Parse'), 0, wx.CENTER)
self.Bind(wx.EVT_BUTTON, self.OnChoose, id=100)
self.Bind(wx.EVT_BUTTON, self.OnParse, id=101)
self.SetSizer(sizer)
def OnChoose(self, event):
"""
Action to be executed when the 'Choose' button is pressed.
"""
dialog = wx.DirDialog(self, defaultPath=self.lastDirectoryOpen)
if dialog.ShowModal() == wx.ID_OK:
self.textctrl.SetValue(dialog.GetPath())
self.lastDirectoryOpen = dialog.GetPath()
else:
wx.MessageDialog(self, 'Please choose an appropiate directory', style=wx.OK).ShowModal()
def OnParse(self, event):
"""
Action to be executed when the 'Parse' button is pressed.
"""
path = self.textctrl.GetValue()
if os.path.isdir(path):
LogParser.getInstance().parseDirectory(path)
self.mainPanel.logDataUpdated()
LogParser.deleteInstance()
else:
wx.MessageDialog(self, 'That directory does not exist', style=wx.OK).ShowModal()

Binary file not shown.

View File

@ -0,0 +1,184 @@
"""
Module IncorrectSequenceList
"""
import wx
from loganalyzerengine.Connection import Connection
from loganalyzerengine.LogFile import LogFile
def advisoryString(message):
"""
Helper method
"""
if message.advisory:
return ' (ADVISORY)'
else:
return ''
class IncorrectSequenceList(wx.ListCtrl):
"""
List of the incorrect events detected after parsing the logs.
"""
def __init__(self, parent):
"""
Constructor
"""
wx.ListCtrl.__init__(self, parent, -1,
style=wx.LC_REPORT|wx.LC_SINGLE_SEL|wx.LC_HRULES|wx.LC_VRULES)
self.incorrectSequencePanel = parent
self.datapresent = False
self.connectionForFilter = None
self.InsertColumn(0, 'Type')
self.InsertColumn(1, 'Connection id / File')
self.InsertColumn(2, 'Message id (prod id | prod seq id)')
self.InsertColumn(3, 'ntimes')
self.InsertColumn(4, 'timestamps')
self.SetColumnWidth(0, 150)
self.SetColumnWidth(1, 250)
self.SetColumnWidth(2, 300)
self.SetColumnWidth(3, 100)
self.SetColumnWidth(4, 300)
def logDataUpdated(self):
"""
This method must be called to notify the list that the parsed data
has changed.
"""
self.datapresent = True
self.updateList()
def checkConnectionForFilter(self):
"""
Returns True if the connection that was inputed by the user
to filter the events is valid.
self.connectionForFilter is a (string, boolean) tuple.
The boolean value is True if the string is a shortId, and False if it is a long id.
"""
if self.connectionForFilter is None:
return False
if self.connectionForFilter[1]:
#shortId
return self.connectionForFilter[0].isdigit() and \
int(self.connectionForFilter[0]) > -1 and \
int(self.connectionForFilter[0]) < len(Connection.connectionIdList)
else:
#longId
return self.connectionForFilter[0] in Connection.connections
def updateList(self):
"""
Updates the display of the list of incorrect events
"""
self.DeleteAllItems()
if self.datapresent:
options = self.incorrectSequencePanel.options
row = 0
# we construct a list of connection long ids to be displayed,
# depending on the filter desired
if self.checkConnectionForFilter():
if self.connectionForFilter[1]:
#shortId
connectionIds = [Connection.connectionIdList[int(self.connectionForFilter[0])]]
else:
connectionIds = [self.connectionForFilter[0]]
else:
if self.connectionForFilter is None or self.connectionForFilter[0] == '':
connectionIds = Connection.connections.keys()
else:
connectionIds = []
# we display the problems tied to connections
showShortIDs = options['showShortIds']
for longId in connectionIds:
# we display long or short ids depending on the option chosen
connection = Connection.getConnectionByLongId(longId)
errors = connection.getErrors()
if showShortIDs:
printedConnectionId = connection.shortId
else:
printedConnectionId = longId
# sent but not received messages
if options['sentButNotReceived']:
for storedMessage, n, timestamps in errors[0]:
message = storedMessage[0]
self.insertRow(row, 'sentButNotReceived' + advisoryString(message), printedConnectionId,
message.producer.shortId if showShortIDs else message.producer.longId,
message.prodSeqId, n, timestamps, wx.WHITE)
row += 1
# received but not sent messages
if options['receivedButNotSent']:
for storedMessage, n, timestamps in errors[1]:
message = storedMessage[0]
self.insertRow(row, 'receivedButNotSent' + advisoryString(message), printedConnectionId,
message.producer.shortId if showShortIDs else message.producer.longId,
message.prodSeqId, n, timestamps, wx.WHITE)
row += 1
# duplicate sent or received messages through a connection
if options['duplicateInConnection']:
for storedMessage, n, timestamps in errors[2]:
message = storedMessage[0]
self.insertRow(row, 'duplicateSentInConnection' + advisoryString(message), printedConnectionId,
message.producer.shortId if showShortIDs else message.producer.longId,
message.prodSeqId, n, timestamps, wx.WHITE)
row += 1
for storedMessage, n, timestamps in errors[3]:
message = storedMessage[0]
self.insertRow(row, 'duplicateReceivedInConnection' + advisoryString(message), printedConnectionId,
message.producer.shortId if showShortIDs else message.producer.longId,
message.prodSeqId, n, timestamps, wx.WHITE)
row += 1
# duplicate sent or received messages in the same log file.
# right now they are only shown when the connection filter is not used.
if options['duplicateInFile'] and not self.checkConnectionForFilter() and \
(self.connectionForFilter is None or self.connectionForFilter[0] == ''):
for logfile in LogFile.logfiles:
errors = logfile.getErrors()
for message, n, timestamps in errors[0]:
self.insertRow(row, 'duplicateSentInFile' + advisoryString(message), str(logfile),
message.producer.shortId if showShortIDs else message.producer.longId,
message.prodSeqId, n, timestamps, wx.WHITE)
row += 1
for message, n, timestamps in errors[1]:
self.insertRow(row, 'duplicateReceivedInFile' + advisoryString(message), str(logfile),
message.producer.shortId if showShortIDs else message.producer.longId,
message.prodSeqId, n, timestamps, wx.WHITE)
row += 1
def insertRow(self, rownumber, typeOfError, connectionId, producerId, producerSequenceId, n, timestamps, col):
"""
Helper method to insert a row into the list
"""
self.InsertStringItem(rownumber, typeOfError)
self.SetStringItem(rownumber, 1, str(connectionId))
self.SetStringItem(rownumber, 2, str(producerId) + ' | ' + str(producerSequenceId))
self.SetStringItem(rownumber, 3, str(n))
self.SetStringItem(rownumber, 4, ' | '.join(timestamps))
self.SetItemBackgroundColour(rownumber, col)

Binary file not shown.

View File

@ -0,0 +1,135 @@
"""
Module IncorrectSequencePanel
"""
import wx
from IncorrectSequenceList import IncorrectSequenceList
class IncorrectSequencePanel(wx.Panel):
"""
This panel contains a list of incorrect events dectected by the parsing,
and many controls to filter which events appear.
Also the user can change if long ids (original ActiveMQConnection id strings, long)
or short ids (a different integer for each connection) is desired.
"""
def __init__(self, parent):
"""
Constructor
"""
wx.Panel.__init__(self, parent, -1)
self.parent = parent
self.options = {}
self.incorrectSequenceList = IncorrectSequenceList(self)
self.showShortIds = wx.CheckBox(self, 100, 'Show results with short ids')
self.sentButNotReceived = wx.CheckBox(self, 101, 'Sent but not received')
self.receivedButNotSent = wx.CheckBox(self, 102, 'Received but not sent')
self.duplicateInConnection = wx.CheckBox(self, 103, 'Duplicate in connection')
self.duplicateInFile = wx.CheckBox(self, 104, 'Duplicate in log file')
self.connectionText = wx.TextCtrl(self, -1, '')
self.rbshortId = wx.RadioButton(self, -1, style=wx.RB_GROUP, label="Short id")
self.rblongId = wx.RadioButton(self, -1, label="Long id")
self.showShortIds.SetValue(True)
self.sentButNotReceived.SetValue(True)
self.receivedButNotSent.SetValue(True)
self.duplicateInConnection.SetValue(True)
self.duplicateInFile.SetValue(True)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer2 = wx.GridSizer()
sizer2 = wx.GridSizer(2, 2, 5, 5)
sizer2.AddMany([self.sentButNotReceived,
self.receivedButNotSent,
self.duplicateInConnection,
self.duplicateInFile
])
sizer3 = wx.BoxSizer(wx.HORIZONTAL)
sizerrb = wx.BoxSizer(wx.VERTICAL)
sizerrb.Add(self.rbshortId, 0, wx.DOWN, 5)
sizerrb.Add(self.rblongId, 0)
sizer3.Add(wx.StaticText(self, -1, 'Filter by connection\n(leave blank to view all)'), 0, wx.CENTER|wx.RIGHT, 5)
sizer3.Add(self.connectionText, 1, wx.CENTER|wx.RIGHT, 5)
sizer3.Add(sizerrb, 0, wx.CENTER|wx.RIGHT, 5)
sizer3.Add(wx.Button(self, 105, 'Filter'), 0, wx.CENTER)
sizer4 = wx.BoxSizer(wx.HORIZONTAL)
sizer4.Add(sizer2, 0, wx.CENTER)
sizer4.Add(sizer3, 1, wx.EXPAND|wx.CENTER|wx.LEFT, 20)
sizer.Add(sizer4, 0, wx.EXPAND|wx.ALL, 5)
sizer5 = wx.BoxSizer(wx.HORIZONTAL)
sizer5.Add(self.showShortIds, 0, wx.RIGHT|wx.CENTER, 5)
sizer5.Add(wx.Button(self, 106, 'Jump to Message Browsing'), 0, wx.CENTER)
sizer.Add(sizer5, 0, wx.ALL, 5)
sizer.Add(self.incorrectSequenceList, 1, wx.EXPAND)
self.Bind(wx.EVT_CHECKBOX, self.OptionsChanged, id=100)
self.Bind(wx.EVT_CHECKBOX, self.OptionsChanged, id=101)
self.Bind(wx.EVT_CHECKBOX, self.OptionsChanged, id=102)
self.Bind(wx.EVT_CHECKBOX, self.OptionsChanged, id=103)
self.Bind(wx.EVT_CHECKBOX, self.OptionsChanged, id=104)
self.Bind(wx.EVT_BUTTON, self.OnFilter, id=105)
self.Bind(wx.EVT_BUTTON, self.OnJump, id=106)
self.parseOptions()
self.SetSizer(sizer)
def logDataUpdated(self):
"""
This method must be called to notify the panel that the parsed data has been updated.
It will in turn notify the list.
"""
self.incorrectSequenceList.logDataUpdated()
def parseOptions(self):
"""
Stores the values of the various checkboxes into self.options.
"""
self.options['showShortIds'] = self.showShortIds.IsChecked()
self.options['sentButNotReceived'] = self.sentButNotReceived.IsChecked()
self.options['receivedButNotSent'] = self.receivedButNotSent.IsChecked()
self.options['duplicateInConnection'] = self.duplicateInConnection.IsChecked()
self.options['duplicateInFile'] = self.duplicateInFile.IsChecked()
def OptionsChanged(self, event):
"""
Action to be executed every time one of the checkboxes is clicked.
It calls parseOptions() and then updates the display of incorrect events.
"""
self.parseOptions()
self.incorrectSequenceList.updateList()
def OnFilter(self, event):
"""
Action to be executed every time the button 'filter' is pressed.
"""
self.incorrectSequenceList.connectionForFilter = (self.connectionText.GetValue(), self.rbshortId.GetValue())
self.OptionsChanged(event)
def OnJump(self, event):
"""
Action to be executed when the 'jump' button is pressed.
"""
if self.incorrectSequenceList.GetFirstSelected() != -1:
connectionId, messageId = self.incorrectSequenceList.GetItem(self.incorrectSequenceList.GetFirstSelected(), 2).GetText().split(' | ')
self.parent.GetParent().browsingMessagesPanel.textctrl1.SetValue(connectionId)
self.parent.GetParent().browsingMessagesPanel.textctrl2.SetValue(messageId)
self.parent.GetParent().browsingMessagesPanel.rbshortId.SetValue(self.showShortIds.GetValue())
self.parent.GetParent().browsingMessagesPanel.rblongId.SetValue(not self.showShortIds.GetValue())
self.parent.GetParent().browsingMessagesPanel.displayMessageInfo(event)
self.parent.SetSelection(1)

Binary file not shown.

View File

@ -0,0 +1,66 @@
"""
Module MessageTravelPanel
"""
import wx
from MessageTravelText import MessageTravelText
class MessageTravelPanel(wx.Panel):
"""
The function of this panel is to show the travel history of a message.
This means the connections that the message went through.
"""
def __init__(self, parent):
"""
Constructor
"""
wx.Panel.__init__(self, parent, -1)
self.rbshortId = wx.RadioButton(self, -1, style=wx.RB_GROUP, label="Short id")
self.rblongId = wx.RadioButton(self, -1, label="Long id")
self.textctrl1 = wx.TextCtrl(self, -1, '')
self.textctrl2 = wx.TextCtrl(self, -1, '')
self.chkshowshortId = wx.CheckBox(self, 100, 'Show result with short ids')
self.messageTravelPanel = MessageTravelText(self)
self.chkshowshortId.SetValue(True)
sizerrb = wx.BoxSizer(wx.VERTICAL)
sizerrb.Add(self.rbshortId, 0, wx.DOWN, 5)
sizerrb.Add(self.rblongId, 0)
sizerinput = wx.BoxSizer(wx.HORIZONTAL)
sizerinput.Add(sizerrb, 0, wx.RIGHT, 5)
sizerinput.Add(wx.StaticText(self, -1, 'Producer id'), 0, wx.CENTER|wx.RIGHT, 5)
sizerinput.Add(self.textctrl1, 2, wx.CENTER|wx.RIGHT, 5)
sizerinput.Add(wx.StaticText(self, -1, 'Producer Sequence id'), 0, wx.CENTER|wx.RIGHT, 5)
sizerinput.Add(self.textctrl2, 1, wx.CENTER|wx.RIGHT, 5)
sizerinput.Add(wx.Button(self, 101 , 'Browse'), 0, wx.CENTER)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(sizerinput, 0, wx.EXPAND|wx.ALL, 5)
sizer.Add(self.chkshowshortId, 0, wx.LEFT|wx.UP, 5)
sizer.Add(self.messageTravelPanel, 1, wx.EXPAND|wx.ALL, 5)
self.Bind(wx.EVT_CHECKBOX, self.displayMessageInfo, id=100)
self.Bind(wx.EVT_BUTTON, self.displayMessageInfo, id=101)
self.SetSizer(sizer)
def displayMessageInfo(self, event):
"""
Action to be executed when the 'Browse' button is pushed.
"""
self.messageTravelPanel.displayMessageInfo(self.textctrl1.GetValue(),
self.textctrl2.GetValue(),
self.rbshortId.GetValue())
def logDataUpdated(self):
"""
This method must be called to notify the panel that the parsed data has been updated.
It will in turn notify the message travel panel.
"""
self.messageTravelPanel.logDataUpdated()

Binary file not shown.

View File

@ -0,0 +1,158 @@
"""
Module MessageTravelText
"""
import wx
from loganalyzerengine.Producer import Producer
from loganalyzerengine.Message import Message
class MessageTravelText(wx.TextCtrl):
"""
Text box where the travel path of a message is displayed.
"""
def __init__(self, parent):
"""
Constructor
"""
wx.TextCtrl.__init__(self, parent, -1, style=wx.TE_MULTILINE)#, style=wx.TE_CENTRE)
self.parent = parent
self.datapresent = False
self.SetEditable(False)
def logDataUpdated(self):
"""
Informs the text control that there is some parsed data.
"""
self.datapresent = True
def displayMessageInfo(self, producerId, prodSeqId, isShortId):
"""
Displays the travel information of a message as text.
connectionId must be a shortId if isShortId == True, and a longId if isShortId == False
"""
if self.datapresent:
# we run some checks on the connection id and command id,
# and transform connectionId into a shortId
if isShortId:
if not producerId.isdigit():
wx.MessageDialog(self, 'That short producer id is not an integer', style=wx.OK).ShowModal()
return
producerId = int(producerId)
if producerId < 0 or producerId > Producer.nProducers - 1:
wx.MessageDialog(self, 'That short producer id does not exist', style=wx.OK).ShowModal()
return
else:
if Producer.exists(producerId):
producerId = Producer.longIdToShortId(producerId)
else:
wx.MessageDialog(self, 'That connection id does not exist', style=wx.OK).ShowModal()
return
if not prodSeqId.isdigit():
wx.MessageDialog(self, 'That command id is not an integer', style=wx.OK).ShowModal()
return
# we ensure the shortId and the commandId are integers
producerId = int(producerId)
prodSeqId = int(prodSeqId)
# we check that the message exists
if Message.exists(Producer.getProducerByShortId(producerId), prodSeqId):
message = Message.getMessage(Producer.getProducerByShortId(producerId), prodSeqId)
sendingFiles, receivingFiles = message.getFiles()
printShortIds = self.parent.chkshowshortId.GetValue()
# we set the value of the text field
self.SetValue(
"\n".join(['Message Id:',
'\tProducer Id: ' + str(producerId if printShortIds else Producer.shortIdToLongId(producerId)),
'\tProducer Sequence id: ' + str(prodSeqId),
'ADVISORY' if message.advisory else '(not advisory)',
'Connections that sent this message:',
#one line for every connection that sent a message
"\n".join([''.join([
'\t',
# if direction == True, message went from connection.fromFile to connection.toFile
str(connection.shortId if printShortIds else connection.longId),
''.join([
', from ',
str(connection.fromFile)
if direction else
str(connection.toFile)
,
' to ',
str(connection.toFile)
if direction else
str(connection.fromFile),
', ',
' | '.join([''.join([
'ConID: ',
str(connection.shortId if printShortIds else connection.longId),
', CommandID: ',
str(commandid),
', ',
timestamp
])
for (connection, commandid, timestamp) in values
])
])
])
for (connection, direction), values in message.sendingConnections.iteritems()
]),
'Connections that received this message:',
#one line for every connection that received a message
"\n".join([''.join([
'\t',
# if direction == True, message went from connection.fromFile to connection.toFile
str(connection.shortId if printShortIds else connection.longId),
''.join([
', from ',
str(connection.fromFile)
if direction else
str(connection.toFile)
,
' to ',
str(connection.toFile)
if direction else
str(connection.fromFile)
,
', ',
' | '.join([''.join([
'ConID: ',
str(connection.shortId if printShortIds else connection.longId),
', CommandID: ',
str(commandid),
', ',
timestamp
])
for (connection, commandid, timestamp) in values
])
])
])
for (connection, direction), values in message.receivingConnections.iteritems()
]),
'Log files where this message was sent:',
'\t' + ", ".join([str(f) for f in sendingFiles]),
'Log files where this message was received:',
'\t' + ", ".join([str(f) for f in receivingFiles])
])
)
else:
# the message doesn't exist
wx.MessageDialog(self, 'That message does not exist', style=wx.OK).ShowModal()
return
else:
# there is no data present
wx.MessageDialog(self, 'Please parse some files first', style=wx.OK).ShowModal()
return

Binary file not shown.

View File

@ -0,0 +1,56 @@
"""
Module TabbedPanel
"""
import wx
from IncorrectSequencePanel import IncorrectSequencePanel
from MessageTravelPanel import MessageTravelPanel
from ViewClientsPanel import ViewClientsPanel
from ViewConnectionsPanel import ViewConnectionsPanel
from ViewFilesPanel import ViewFilesPanel
class TabbedPanel(wx.Panel):
"""
Panel with the tabs that will display the information once the log files are parsed.
It contains 4 tabs, via a wx.Notebook object:
-IncorrectSequencePanel.
-BrowsingMessagesPanel.
-ViewConnectionsPanel.
-ViewFilesPanel.
"""
def __init__(self, parent):
"""
Constructor
"""
wx.Panel.__init__(self, parent, -1)
notebook = wx.Notebook(self, -1)
self.incorrectSequencePanel = IncorrectSequencePanel(notebook)
self.browsingMessagesPanel = MessageTravelPanel(notebook)
self.viewClientsPanel = ViewClientsPanel(notebook)
self.viewConnectionsPanel = ViewConnectionsPanel(notebook)
self.viewFilesPanel = ViewFilesPanel(notebook)
notebook.AddPage(self.incorrectSequencePanel, 'Incorrect Sequences')
notebook.AddPage(self.browsingMessagesPanel, 'Message Browsing')
notebook.AddPage(self.viewClientsPanel, 'View clients')
notebook.AddPage(self.viewConnectionsPanel, 'View connections')
notebook.AddPage(self.viewFilesPanel, 'View files')
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(notebook, 1, wx.EXPAND|wx.ALL, 5)
self.SetSizer(sizer)
def logDataUpdated(self):
"""
When this panel is notified that the parsed data has changed,
it notifies the 4 sub panels.
"""
self.incorrectSequencePanel.logDataUpdated()
self.browsingMessagesPanel.logDataUpdated()
self.viewClientsPanel.logDataUpdated()
self.viewConnectionsPanel.logDataUpdated()
self.viewFilesPanel.logDataUpdated()

Binary file not shown.

View File

@ -0,0 +1,100 @@
"""
Module ViewConnectionsPanel
"""
import wx
from loganalyzerengine.Connection import Connection
from loganalyzerengine.Producer import Producer
from loganalyzerengine.Consumer import Consumer
class ViewClientsPanel(wx.Panel):
"""
This panel shows the list of connections that appear in the log files.
Also, it enables the user to copy the long id of a connection to the system clipboard,
and to 'jump' to the IncorrectSequencePanel, filtering the display so that only the
events of that connection are displayed.
"""
def __init__(self, parent):
"""
Constructor
"""
wx.Panel.__init__(self, parent, -1)
self.notebook = parent
sizer = wx.BoxSizer(wx.VERTICAL)
self.producerList = wx.ListCtrl(self, -1,
style=wx.LC_REPORT|wx.LC_SINGLE_SEL|wx.LC_HRULES|wx.LC_VRULES|wx.LC_EDIT_LABELS)
self.producerList.InsertColumn(0, 'Short id')
self.producerList.InsertColumn(1, 'Short Connection id')
self.producerList.InsertColumn(2, 'Session Id')
self.producerList.InsertColumn(3, 'Value')
self.producerList.InsertColumn(4, 'Long Connection id')
self.producerList.SetColumnWidth(0, 80)
self.producerList.SetColumnWidth(1, 120)
self.producerList.SetColumnWidth(2, 80)
self.producerList.SetColumnWidth(3, 80)
self.producerList.SetColumnWidth(4, 500)
self.consumerList = wx.ListCtrl(self, -1,
style=wx.LC_REPORT|wx.LC_SINGLE_SEL|wx.LC_HRULES|wx.LC_VRULES|wx.LC_EDIT_LABELS)
self.consumerList.InsertColumn(0, 'Short id')
self.consumerList.InsertColumn(1, 'Short Connection id')
self.consumerList.InsertColumn(2, 'Session Id')
self.consumerList.InsertColumn(3, 'Value')
self.consumerList.InsertColumn(4, 'Long Connection id')
self.consumerList.SetColumnWidth(0, 80)
self.consumerList.SetColumnWidth(1, 120)
self.consumerList.SetColumnWidth(2, 80)
self.consumerList.SetColumnWidth(3, 80)
self.consumerList.SetColumnWidth(4, 500)
sizer.Add(wx.StaticText(self, -1, 'Producers'), 0, wx.CENTER|wx.LEFT|wx.TOP|wx.RIGHT, 15)
sizer.Add(self.producerList, 1, wx.EXPAND|wx.ALL, 5)
sizer.Add(wx.StaticText(self, -1, 'Consumers'), 0, wx.CENTER|wx.LEFT|wx.TOP|wx.RIGHT, 15)
sizer.Add(self.consumerList, 1, wx.EXPAND|wx.ALL, 5)
self.SetSizer(sizer)
def logDataUpdated(self):
"""
Informs this panel that new data has been parsed,
and that the list of connections should be updated.
"""
self.producerList.DeleteAllItems()
self.consumerList.DeleteAllItems()
shortId = 0
for longId in Producer.producerIdList:
producer = Producer.getProducerByLongId(longId)
self.insertRow(self.producerList, shortId, Connection.longIdToShortId(producer.connectionId),
producer.sessionId, producer.value,
Connection.getConnectionByLongId(producer.connectionId))
shortId += 1
shortId = 0
for longId in Consumer.consumerIdList:
consumer = Consumer.getConsumerByLongId(longId)
self.insertRow(self.consumerList, shortId, Connection.longIdToShortId(consumer.connectionId),
consumer.sessionId, consumer.value,
Connection.getConnectionByLongId(consumer.connectionId))
shortId += 1
def insertRow(self, targetList, shortId, shortConnectionId, sessionId, value, longConnectionId):
"""
Helper method to insert a new row in the list of connections.
"""
targetList.InsertStringItem(shortId, str(shortId))
targetList.SetStringItem(shortId, 1, str(shortConnectionId))
targetList.SetStringItem(shortId, 2, str(sessionId))
targetList.SetStringItem(shortId, 3, str(value))
targetList.SetStringItem(shortId, 4, str(longConnectionId))

Binary file not shown.

View File

@ -0,0 +1,103 @@
"""
Module ViewConnectionsPanel
"""
import wx
from loganalyzerengine.Connection import Connection
class ViewConnectionsPanel(wx.Panel):
"""
This panel shows the list of connections that appear in the log files.
Also, it enables the user to copy the long id of a connection to the system clipboard,
and to 'jump' to the IncorrectSequencePanel, filtering the display so that only the
events of that connection are displayed.
"""
def __init__(self, parent):
"""
Constructor
"""
wx.Panel.__init__(self, parent, -1)
self.notebook = parent
sizer = wx.BoxSizer(wx.VERTICAL)
self.list = wx.ListCtrl(self, -1,
style=wx.LC_REPORT|wx.LC_SINGLE_SEL|wx.LC_HRULES|wx.LC_VRULES|wx.LC_EDIT_LABELS)
self.list.InsertColumn(0, 'Short id')
self.list.InsertColumn(1, 'Long id')
self.list.InsertColumn(2, 'Connection established FROM file')
self.list.InsertColumn(3, 'Connection established TO file')
self.list.InsertColumn(4, 'Producers')
self.list.InsertColumn(5, 'Consumers')
self.list.SetColumnWidth(0, 80)
self.list.SetColumnWidth(1, 250)
self.list.SetColumnWidth(2, 200)
self.list.SetColumnWidth(3, 200)
sizer2 = wx.BoxSizer(wx.HORIZONTAL)
sizer2.Add(wx.Button(self, 100, 'Copy selected long id to clipboard'), 0, wx.RIGHT, 5)
sizer2.Add(wx.Button(self, 101, "Jump to this connection's problems"))
sizer.Add(sizer2, 0, wx.ALL, 5)
sizer.Add(self.list, 1, wx.EXPAND|wx.LEFT|wx.BOTTOM|wx.RIGHT, 5)
self.Bind(wx.EVT_BUTTON, self.OnCopy, id=100)
self.Bind(wx.EVT_BUTTON, self.OnJump, id=101)
self.SetSizer(sizer)
def logDataUpdated(self):
"""
Informs this panel that new data has been parsed,
and that the list of connections should be updated.
"""
self.list.DeleteAllItems()
shortId = 0
for longId in Connection.connectionIdList:
connection = Connection.getConnectionByLongId(longId)
self.insertRow(shortId, longId, connection.fromFile, connection.toFile, connection.producers, connection.consumers)
shortId += 1
def insertRow(self, shortId, longId, fromFile, to, producers, consumers):
"""
Helper method to insert a new row in the list of connections.
"""
self.list.InsertStringItem(shortId, str(shortId))
self.list.SetStringItem(shortId, 1, str(longId))
self.list.SetStringItem(shortId, 2, str(fromFile))
self.list.SetStringItem(shortId, 3, str(to))
self.list.SetStringItem(shortId, 4, ", ".join(str(p.shortId) for p in producers))
self.list.SetStringItem(shortId, 5, ", ".join(str(c.shortId) for c in consumers))
def OnCopy(self, event):
"""
Action to be executed when pressing the 'Copy selected long id to clipboard' button.
The longId of the selected connection will be copied to the clipboard.
"""
shortId = self.list.GetFirstSelected()
if shortId != -1 and wx.TheClipboard.Open():
wx.TheClipboard.SetData(wx.TextDataObject(str(Connection.connectionIdList[shortId])))
wx.TheClipboard.Close()
def OnJump(self, event):
"""
Action to be executed when pressing the 'Jump to this connection's problems' button.
The tab with the 'IncorrectSequencePanel' will be selected and the list of incorrect
events will be automatically filtered by the selected connection.
"""
shortId = self.list.GetFirstSelected()
if shortId != -1:
incorrectSequencePanel = self.notebook.GetParent().incorrectSequencePanel
incorrectSequencePanel.connectionText.SetValue(str(shortId))
incorrectSequencePanel.rbshortId.SetValue(True)
incorrectSequencePanel.rblongId.SetValue(False)
incorrectSequencePanel.OnFilter(event)
self.notebook.SetSelection(0)

Binary file not shown.

View File

@ -0,0 +1,45 @@
"""
Module ViewFilesPanel
"""
import wx
from loganalyzerengine.LogFile import LogFile
class ViewFilesPanel(wx.Panel):
"""
This panel shows the list of log files that have been read.
"""
def __init__(self, parent):
"""
Constructor
"""
wx.Panel.__init__(self, parent, -1)
sizer = wx.BoxSizer(wx.VERTICAL)
self.text = wx.TextCtrl(self, -1, style=wx.TE_MULTILINE)
sizer.Add(self.text, 1, wx.EXPAND|wx.ALL, 5)
self.SetSizer(sizer)
def logDataUpdated(self):
"""
The panel is informed that new data has been parsed,
and the list of files should be updated.
"""
self.text.SetValue(
'\n'.join(
'\n'.join([
str(file),
'\tConnections established from this file:',
'\n'.join(['\t\t' + str(con.shortId) + ' ' + str(con.longId) for con in file.outgoing]),
'\tConnections established to this file:',
'\n'.join(['\t\t' + str(con.shortId) + ' ' + str(con.longId) for con in file.incoming])
])
for file in LogFile.logfiles)
)

Binary file not shown.

View File

Binary file not shown.

1
log_analyzer_tool/run.bat Executable file
View File

@ -0,0 +1 @@
python Main.py

1
log_analyzer_tool/run.sh Executable file
View File

@ -0,0 +1 @@
python Main.py

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB