mirror of https://github.com/apache/activemq.git
Added the log analyser tool to svn - for http://issues.apache.org/activemq/browse/AMQ-1361
git-svn-id: https://svn.apache.org/repos/asf/activemq/trunk@565392 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
2edbcd6298
commit
09f8ac5542
|
@ -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()
|
|
@ -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.
|
|
@ -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.
|
@ -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.
|
@ -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.
|
@ -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.
|
@ -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.
|
@ -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.
|
@ -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.
|
@ -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.
|
@ -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.
|
@ -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.
|
@ -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.
|
@ -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.
|
@ -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.
|
@ -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.
|
@ -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.
|
@ -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.
Binary file not shown.
|
@ -0,0 +1 @@
|
||||||
|
python Main.py
|
|
@ -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 |
Loading…
Reference in New Issue