blob: a9042a3b8fd4e9a7e726b3b3a237c4e5c5d8a560 [file] [log] [blame]
#!/usr/bin/env python
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
from __future__ import unicode_literals
from __future__ import division
from __future__ import absolute_import
from __future__ import print_function
from datetime import *
import re
import sys
import traceback
import test_data as td
import common
import text
import router
"""
Sequence output copies the tailing end of transfers to a second display line.
Define how many characters to show.
"""
SEQUENCE_TRANSFER_SIZE = 50
def colorize_bg(what):
# TODO: use the real colorize_bg
return what
def proton_split(line):
"""
Split a log line into fields.
* allow commas and spaces in quoted strings.
* split on ', ' and on ' '.
strip trailing commas between fields.
* quoted fields must have both quotes
:param line:
:return:
"""
result = []
indqs = False
pending_comma = False
res = ""
for i in range(len(line)):
c = line[i]
if c == '\"':
if pending_comma:
res += ','
pending_comma = False
indqs = not indqs
res += c
elif c == ',':
if pending_comma:
res += c
pending_comma = True
elif c == ' ':
if indqs:
if pending_comma:
res += ','
pending_comma = False
res += c
else:
if res != '':
if pending_comma:
pending_comma = False
result.append(res)
res = ''
else:
res += c
if res != '':
result.append(str(res))
if indqs:
raise ValueError("SPLIT ODD QUOTES: %s", line)
# print ("SPLIT: line: %s" % line)
# print ("SPLIT: flds: %s" % result)
return result
class LogLineData:
def direction_is_in(self):
return self.direction == text.direction_in()
def direction_is_out(self):
return self.direction == text.direction_out()
def __init__(self):
self.web_show_str = ""
self.sdorg_str = "" # text for sequence diagram source
self.name = ""
self.conn_num = "" # source router's undecorated conn num
self.conn_id = "" # decorated routerPrefixLetter'instanceNumber-conn_num
self.conn_peer = "" # display name of peer in seen in Open 'A - routerId.Test'
self.channel = "" # undecorated number - '0'
self.direction = "" # '<-' IN, or '->' OUT, or '--'
self.described_type = DescribedType() # DescribedType object
self.handle = "" # undecorated number - '1'
self.delivery_id = "" # "0"
self.delivery_tag = "" # "00:00:00:00"
self.remote = "" # undecorated number - '2'
self.channel_handle = "" # decorated - '[0,0]'
self.channel_remote = "" # decorated - '[1,2]'
self.flow_deliverycnt = "" # undecorated number - '50'
self.flow_linkcredit = "" # undecorated number - '100'
self.flow_cnt_credit = "" # decorated - '(50,100)'
self.flow_drain = False
self.transfer_id = ""
self.role = ""
self.is_receiver = False
self.source = ""
self.target = ""
self.first = "" # undecorated number - '10'
self.last = "" # undecorated number - '20'
self.settled = "" # Disposition or Transfer settled field from log line
self.disposition_state = "?absent?"
self.snd_settle_mode = "" # Attach
self.rcv_settle_mode = "" # Attach
self.transfer = False
self.transfer_data = "" # protonized transfer data value
self.transfer_bare = "" # bare message from transfer_data
self.transfer_hdr_annos = "" # header and annotation sections
self.transfer_size = "" # size declared by number in parenthesis
self.transfer_short_name = ""
self.transfer_settled = False
self.transfer_presettled = False
self.transfer_more = False
self.transfer_resume = False
self.transfer_aborted = False
self.transfer_exhausted_credit = False
self.link_short_name = ""
self.link_short_name_popup = ""
self.is_policy_trace = False # line is POLICY (trace)
self.is_server_info = False # line is SERVER (info)
self.is_router_ls = False # line is ROUTER_LS (info)
self.is_scraper = False # line is SCRAPER (any-level)
self.fid = "" # Log line (frame) id as used in javascript code
self.amqp_error = False
self.link_class = "client" # attach sees: normal, router, router-data (, management?)
self.disposition_display = ""
self.final_disposition = None
self.no_parent_link = False
class DescribedType:
"""
Given a line like:
@typename(00) [key1=val1, ...]
Extract the typename and create a map of the key-val pairs
May recursively find embedded described types
"""
@staticmethod
def is_dtype_name(name):
"""
Return true if the name is a pn_trace described type name
:param name:
:return:
"""
return (name.startswith('@') and
'(' in name and
name.endswith(')'))
@staticmethod
def get_key_and_val(kvp):
eqi = kvp.find('=')
return kvp[:eqi], kvp[eqi + 1:]
@staticmethod
def dtype_name(name):
if not DescribedType.is_dtype_name(name):
raise ValueError("Name '%s' is not a described type name" % name)
return name[1: name.find('(')]
@staticmethod
def dtype_number(name):
if not DescribedType.is_dtype_name(name):
raise ValueError("Name '%s' is not a described type name" % name)
return int(name[name.find('(') + 1: -1])
def __init__(self):
self.dict = {}
self.dtype_name = "unparsed"
self.dtype_number = 0
def __repr__(self):
return self._representation()
def _representation(self):
return "DescribedType %s( %d ) : %s" % (self.dtype_name, self.dtype_number, self.dict)
def add_field_to_dict(self, f_text, expected_key=None):
if '=' not in f_text:
raise ValueError("Field does not contain equal sign '%s'" % self.line)
if expected_key is not None and not f_text.startswith(expected_key):
raise ValueError("Transfer field %s not in order from line: %s" % (expected_key, self.line))
key, val = DescribedType.get_key_and_val(f_text)
if val.endswith(','):
val = val[:-1]
self.dict[key] = val
def process_transfer_tail_key(self):
keys = ["batchable", "aborted", "resume", "state", "rcv-settle-mode", "more", "settled", "message-format"]
for key in keys:
idx = self.line.rfind(key)
if idx != -1:
field = self.line[idx:]
self.add_field_to_dict(field, key)
self.line = self.line[:idx].strip()
return True
return False
def parseTransfer(self):
"""
Figure out the described type fields for the transfer.
Transfers are handled specially with the ill-formatted binary delivery-tag field
:return:
"""
# strip leading '[' and trailing ']'
if not (self.line.startswith('[') and self.line.endswith(']')):
raise ValueError("Described type not delimited with square brackets: '%s'" % self.line)
self.line = self.line[1:]
self.line = self.line[:-1]
# process fields from head
fHandle = self.line.split()[0]
self.add_field_to_dict(fHandle)
self.line = self.line[(len(fHandle) + 1):]
try:
fDelId = self.line.split()[0]
self.add_field_to_dict(fDelId)
self.line = self.line[(len(fDelId) + 1):]
# process fields from tail
while len(self.line) > 0 and self.process_transfer_tail_key():
pass
# the remainder, no matter how unlikely, must be the delivery-tag
self.add_field_to_dict(self.line, "delivery-tag")
except:
# delivery-id and delivery-tag are optional in subsequent transfers
# when more=true.
pass
def parse_dtype_line(self, _dtype, _line):
"""
Figure out the fields for the described type.
The line format is:
Transfers are handled specially with the ill-formatted binary delivery-tag field
Note other performatives with ill-formatted binary data might get rejected. We
only struggle figuring out the delivery-tag because it happens so often.
:param _dtype: @describedtypename(num)
:param _line: [key=val [, key=val]...]
:return:
"""
self.dtype = _dtype
self.line = str(_line)
self.dtype_name = DescribedType.dtype_name(self.dtype)
self.dtype_number = DescribedType.dtype_number(self.dtype)
# Process transfers separately..
# Transfer perfomatives will not call parse recursively while others might
if self.dtype_name == "transfer":
self.parseTransfer()
return
# strip leading '[' and trailing ']'
if not (self.line.startswith('[') and self.line.endswith(']')):
raise ValueError("Described type not delimited with square brackets: '%s'" % _line)
self.line = self.line[1:]
self.line = self.line[:-1]
# process fields
fields = proton_split(self.line)
while len(fields) > 0 and len(fields[0]) > 0:
if '=' not in fields[0]:
raise ValueError("Field does not contain equal sign '%s'" % fields[0])
key, val = DescribedType.get_key_and_val(fields[0])
del fields[0]
if DescribedType.is_dtype_name(val):
# recursing to process subtype
# pull subtype's data out of fields. The fields list belongs to parent.
subfields = []
if fields[0] == "[]":
# degenerate case of empty subtype closing parent type
# @disposition .. state=@accepted(36) []]
subfields.append("[]")
del fields[0]
else:
# While extracting this type's fields, include nested described types
# and PN_SYMBOL data enclosed in brackets. Current type ends when close
# bracket seen and nest level is zero.
nest = 0
while len(fields) > 0:
if "=@" in fields[0] and "]" not in fields[0] and "=@:" not in fields[0]:
nest += 1
if nest == 0:
if fields[0].endswith('],'):
subfields.append(fields[0][:-2])
subfields.append(']')
del fields[0]
break
if fields[0].endswith(']'):
subfields.append(fields[0][:-1])
subfields.append(']')
del fields[0]
break
elif fields[0].endswith('],') or fields[0].endswith(']'):
nest -= 1
if fields[0].endswith(']]'):
subfields.append(fields[0])
del fields[0]
break
subfields.append(fields[0])
del fields[0]
subtype = DescribedType()
subtype.parse_dtype_line(val, ' '.join(subfields))
self.dict[key] = subtype
elif val.startswith("@PN_SYMBOL"):
# symbols may end in first field or some later field
while not val.endswith(']'):
val += fields[0]
del fields[0]
self.dict[key] = val
elif val.startswith('{'):
# handle some embedded map: properties={:product=\"qpid-dispatch-router\", :version=\"1.3.0-SNAPSHOT\"}
# pull subtype's data out of fields. The fields list belongs to parent.
submap = {}
fields.insert(0, val)
skey, sval = DescribedType.get_key_and_val(fields[0][1:])
submap[skey] = sval
del fields[0]
while len(fields) > 0:
if fields[0].endswith('},'):
skey, sval = DescribedType.get_key_and_val(fields[0][:-2])
submap[skey] = sval
del fields[0]
break
if fields[0].endswith('}'):
skey, sval = DescribedType.get_key_and_val(fields[0][:-1])
submap[skey] = sval
del fields[0]
break
skey, sval = DescribedType.get_key_and_val(fields[0])
submap[skey] = sval
del fields[0]
self.dict[key] = submap
else:
self.dict[key] = val
class ParsedLogLine(object):
"""
Grind through the log line and record some facts about it.
* Constructor returns Null if the log line is to be ignored
* Constructor args:
** log_index 0 for 'A', 1 for 'B'
** routerInstance which instance in log file
** lineno line number
** line the log line
** common common block object
"""
server_trace_key = "SERVER (trace) ["
protocol_trace_key = "PROTOCOL (trace) ["
server_info_key = "SERVER (info) ["
policy_trace_key = "POLICY (trace) ["
router_ls_key = "ROUTER_LS (info)"
transfer_key = "@transfer(20)"
proton_frame_key = "FRAME: "
def sender_settle_mode_of(self, value):
if value == "0":
return "unsettled(0)"
elif value == "1":
return "settled(1)"
elif value == "2":
return "mixed(2)" # default
else:
return "unknown(%s) % value"
def receiver_settle_mode_of(self, value):
if value == "0":
return "first(0)"
elif value == "1":
return "second(1)"
else:
return "unknown(%s) % value"
def resdict_value(self, resdict, key, if_absent):
return resdict[key] if key in resdict else if_absent
def highlighted(self, name, value, color):
result = ""
if value:
result = "<span style=\"background-color:%s\">%s</span>" % (color, name)
return result
def unhighlighted(self, name, value):
return name if value else ""
def extract_facts(self):
perf = self.data.described_type.dtype_number
res = self.data
resdict = self.data.described_type.dict
res.sdorg_str = "HELP I'M A ROCK - Unknown performative: %s" % perf
# the performatives
# Note: res.channel is already populated
if perf == 0x10:
# Performative: open [0] always channel 0
res.name = "open"
res.channel = "0"
res.web_show_str = "<strong>%s</strong> [%s]" % (res.name, res.channel)
res.sdorg_str = "OPEN"
if res.direction == text.direction_in():
res.conn_peer = self.resdict_value(resdict, "container-id", "unknown")
res.web_show_str += (" (peer: %s)" % res.conn_peer)
res.sdorg_str += (" (peer: %s)" % res.conn_peer)
elif perf == 0x11:
# Performative: begin [channel,remoteChannel]
# TODO: This has a bug where the local and remote channel numbers are confused.
# Usually they are the same. See if anyone notices!
# res.channel
res.name = "begin"
res.remote = self.resdict_value(resdict, "remote-channel", "None)")
res.channel_remote = "[%s,%s]" % (res.channel, res.remote)
res.web_show_str = "<strong>%s</strong> %s" % (res.name, res.channel_remote)
res.sdorg_str = "BEGIN %s" % (res.channel_remote)
elif perf == 0x12:
# Performative: attach [channel,handle] role name (source: src, target: tgt)
res.name = "attach"
res.handle = resdict["handle"]
res.role = "receiver" if resdict["role"] == "true" else "sender"
res.is_receiver = res.role == "receiver"
# translated names handled later
name = self.resdict_value(resdict, "name", "None")
self.shorteners.short_link_names.register(name, self)
tmpsrc = self.resdict_value(resdict, "source", None)
tmptgt = self.resdict_value(resdict, "target", None)
res.snd_settle_mode = self.sender_settle_mode_of(
resdict["snd-settle-mode"]) if "snd-settle-mode" in resdict else "mixed"
res.rcv_settle_mode = self.receiver_settle_mode_of(
resdict["rcv-settle-mode"]) if "rcv-settle-mode" in resdict else "first"
caps = ""
if tmpsrc is not None:
res.source = self.resdict_value(tmpsrc.dict, "address", "none")
caps = self.resdict_value(tmpsrc.dict, "capabilities", "")
else:
res.source = "none"
if tmptgt is not None:
res.target = self.resdict_value(tmptgt.dict, "address", "none")
if caps == "":
caps = self.resdict_value(tmptgt.dict, "capabilities", "")
else:
res.target = "none"
res.channel_handle = "[%s,%s]" % (res.channel, res.handle)
if 'qd.router-data' in caps:
res.link_class = 'router-data'
elif 'qd.router' in caps:
res.link_class = 'router'
"""
TODO:
res.source = short_endp_names.translate(res.source)
res.target = short_endp_names.translate(res.target)
res.snd_settle_mode = extract_name(tmpssm)
res.rcv_settle_mode = extract_name(tmprsm)
"""
# show_str handled in post_extract
elif perf == 0x13:
# Performative: flow [channel,handle]
res.name = "flow"
res.handle = resdict["handle"]
res.flow_deliverycnt = self.resdict_value(resdict, "delivery-count", "0")
res.flow_linkcredit = self.resdict_value(resdict, "link-credit", "0")
res.flow_drain = resdict.get("drain", "") == "true"
res.channel_handle = "[%s,%s]" % (res.channel, res.handle)
res.flow_cnt_credit = "(%s,%s)" % (res.flow_deliverycnt, res.flow_linkcredit)
res.web_show_str = "<strong>%s</strong> %s (%s,%s) %s" % (
res.name, colorize_bg(res.channel_handle), res.flow_deliverycnt, res.flow_linkcredit,
self.highlighted("drain", res.flow_drain, common.color_of("drain")))
res.sdorg_str = "FLOW %s (%s,%s)" % (res.channel_handle, res.flow_deliverycnt, res.flow_linkcredit) # TODO: Add drain
elif perf == 0x14:
# Performative: transfer [channel,handle] (id)
res.name = "transfer"
res.transfer = True
res.handle = resdict["handle"]
res.channel_handle = "[%s,%s]" % (res.channel, res.handle)
res.delivery_id = self.resdict_value(resdict, "delivery-id", "none")
res.delivery_tag = self.resdict_value(resdict, "delivery-tag", "none")
res.settled = self.resdict_value(resdict, "settled", "false")
res.transfer_settled = resdict.get("settled", "") == "true"
res.transfer_more = resdict.get("more", "") == "true"
res.transfer_resume = resdict.get("resume", "") == "true"
res.transfer_aborted = resdict.get("aborted", "") == "true"
# translated names handled in post_extract
self.shorteners.short_data_names.register(res.transfer_bare, self)
elif perf == 0x15:
# Performative: disposition [channel] (role first-last)
res.name = "disposition"
res.role = "receiver" if resdict["role"] == "true" else "sender"
res.is_receiver = res.role == "receiver"
res.first = self.resdict_value(resdict, "first", "0")
res.last = self.resdict_value(resdict, "last", res.first)
res.settled = self.resdict_value(resdict, "settled", "false")
state = resdict.get("state")
if state is not None:
res.disposition_state = state.dtype_name
### colorize_dispositions_not_accepted(proto, res, global_vars, count_anomalies)
res.web_show_str = ("<strong>%s</strong> [%s] (%s %s-%s settled=%s state=%s)" %
(res.name, res.channel, res.role, res.first, res.last, res.settled, res.disposition_state))
res.sdorg_str = ("%s [%s] (%s %s-%s settled=%s state=%s)" %
(res.name, res.channel, res.role, res.first, res.last, res.settled, res.disposition_state))
elif perf == 0x16:
# Performative: detach [channel, handle]
res.name = "detach"
res.handle = resdict["handle"]
### TODO: colorize_performative_error(proto, res, global_vars, count_anomalies)
res.channel_handle = "[%s,%s]" % (res.channel, res.handle)
res.web_show_str = "<strong>%s</strong> %s" % (res.name, colorize_bg(res.channel_handle))
res.sdorg_str = "DETACH %s" % (res.channel_handle)
elif perf == 0x17:
# Performative: end [channel]
res.name = "end"
### TODO: colorize_performative_error(proto, res, global_vars, count_anomalies)
res.web_show_str = "<strong>%s</strong> [%s]" % (res.name, res.channel)
res.sdorg_str = "END [%s]" % (res.channel)
elif perf == 0x18:
# Performative: close [0] always channel 0
res.channel = "0"
res.name = "close"
### colorize_performative_error(proto, res, global_vars, count_anomalies)
res.web_show_str = "<strong>%s</strong> [%s]" % (res.name, res.channel)
res.sdorg_str = "CLOSE [%s]" % (res.channel)
elif perf == 0x1d:
# transport:definitions error
res.name = "error"
descr = self.resdict_value(resdict, "description", "none")
res.web_show_str = "<strong>%s</strong> [%s] %s" % (res.name, res.channel, descr)
elif perf == 0x23:
# messaging:delivery-state received
res.name = "received"
res.web_show_str = "<strong>%s</strong> [%s]" % (res.name, res.channel)
elif perf == 0x24:
# messaging:delivery-state accepted
res.name = "accepted"
res.web_show_str = "<strong>%s</strong> [%s]" % (res.name, res.channel)
elif perf == 0x25:
# messaging:delivery-state rejected
res.name = "rejected"
res.web_show_str = "<strong>%s</strong> [%s]" % (res.name, res.channel)
elif perf == 0x26:
# messaging:delivery-state released
res.name = "released"
res.web_show_str = "<strong>%s</strong> [%s]" % (res.name, res.channel)
elif perf == 0x27:
# messaging:delivery-state modified
res.name = "modified"
res.web_show_str = "<strong>%s</strong> [%s]" % (res.name, res.channel)
elif perf == 0x28:
# messaging:addressing source
res.name = "source"
res.web_show_str = "<strong>%s</strong> [%s]" % (res.name, res.channel)
elif perf == 0x29:
# messaging:addressing target
res.name = "target"
res.web_show_str = "<strong>%s</strong> [%s]" % (res.name, res.channel)
elif perf == 0x2b:
# messaging:addressing delete-on-close
res.name = "delete-on-close"
res.web_show_str = "<strong>%s</strong> [%s]" % (res.name, res.channel)
elif perf == 0x2c:
# messaging:addressing delete-on-no-links
res.name = "delete-on-no-links"
res.web_show_str = "<strong>%s</strong> [%s]" % (res.name, res.channel)
elif perf == 0x2d:
# messaging:addressing delete-on-no-messages
res.name = "delete-on-no-messages"
res.web_show_str = "<strong>%s</strong> [%s]" % (res.name, res.channel)
elif perf == 0x2e:
# messaging:addressing delete-on-no-links-or-messages
res.name = "delete-on-no-links-or-messages"
res.web_show_str = "<strong>%s</strong> [%s]" % (res.name, res.channel)
elif perf == 0x30:
# transactions:coordination coordinator
res.name = "coordinator"
res.web_show_str = "<strong>%s</strong> [%s]" % (res.name, res.channel)
elif perf == 0x31:
# transactions:coordination declare
res.name = "declare"
res.web_show_str = "<strong>%s</strong> [%s]" % (res.name, res.channel)
elif perf == 0x32:
# transactions:coordination discharge
res.name = "discharge"
res.web_show_str = "<strong>%s</strong> [%s]" % (res.name, res.channel)
elif perf == 0x33:
# transactions:coordination declared
res.name = "declared"
res.web_show_str = "<strong>%s</strong> [%s]" % (res.name, res.channel)
elif perf == 0x34:
# transactions:coordination transactional-state
res.name = "transactional-state"
res.web_show_str = "<strong>%s</strong> [%s]" % (res.name, res.channel)
elif perf == 0x40:
# security:sasl sasl-mechanisms
res.name = "sasl-mechanisms"
mechs = self.resdict_value(resdict, "sasl-server-mechanisms", "none")
res.web_show_str = "<strong>%s</strong> [%s] %s" % (res.name, res.channel, mechs)
elif perf == 0x41:
# security:sasl sasl-init
res.name = "sasl-init"
mech = self.resdict_value(resdict, "mechanism", "none")
res.web_show_str = "<strong>%s</strong> [%s] %s" % (res.name, res.channel, mech)
elif perf == 0x42:
# security:sasl sasl-challenge
res.name = "sasl-challenge"
res.web_show_str = "<strong>%s</strong> [%s]" % (res.name, res.channel)
elif perf == 0x43:
# security:sasl sasl-response
res.name = "sasl-response"
res.web_show_str = "<strong>%s</strong> [%s]" % (res.name, res.channel)
elif perf == 0x44:
# security:sasl sasl-outcome
res.name = "sasl-outcome"
code = self.resdict_value(resdict, "code", "none")
res.web_show_str = "<strong>%s</strong> [%s] code=%s" % (res.name, res.channel, code)
elif perf == 0x70:
# messaging:message-format header
res.name = "header"
res.web_show_str = "<strong>%s</strong> [%s]" % (res.name, res.channel)
elif perf == 0x71:
# messaging:message-format delivery-annotations
res.name = "delivery-annotations"
res.web_show_str = "<strong>%s</strong> [%s]" % (res.name, res.channel)
elif perf == 0x72:
# messaging:message-format message-annotations
res.name = "message-annotations"
res.web_show_str = "<strong>%s</strong> [%s]" % (res.name, res.channel)
elif perf == 0x73:
# messaging:message-format properties
res.name = "properties"
res.web_show_str = "<strong>%s</strong> [%s]" % (res.name, res.channel)
elif perf == 0x74:
# messaging:message-format application-properties
res.name = "application-properties"
res.web_show_str = "<strong>%s</strong> [%s]" % (res.name, res.channel)
elif perf == 0x75:
# messaging:message-format data
res.name = "data"
res.web_show_str = "<strong>%s</strong> [%s]" % (res.name, res.channel)
elif perf == 0x76:
# messaging:message-format amqp-sequence
res.name = "amqp-sequence"
res.web_show_str = "<strong>%s</strong> [%s]" % (res.name, res.channel)
elif perf == 0x77:
# messaging:message-format amqp-value
res.name = "amqp-value"
res.web_show_str = "<strong>%s</strong> [%s]" % (res.name, res.channel)
elif perf == 0x78:
# messaging:message-format footer
res.name = "footer"
res.web_show_str = "<strong>%s</strong> [%s]" % (res.name, res.channel)
else:
res.web_show_str = "HELP I'M A ROCK - Unknown performative: %s" % perf
if "error" in resdict:
res.amqp_error = True
dct = resdict["error"].dict
condi = dct["condition"]
descr = dct["description"] if "description" in dct else ""
res.web_show_str += (" <span style=\"background-color:%s\">error</span> "
"%s %s" % (common.color_of("errors"), condi, descr))
def post_extract_names(self):
perf = self.data.described_type.dtype_number
res = self.data
resdict = self.data.described_type.dict
if perf == 0x12:
# Performative: attach [channel,handle] role name (source: src, target: tgt)
name = self.resdict_value(resdict, "name", "None")
res.link_short_name_popup = self.shorteners.short_link_names.translate(name, True, customer=self)
res.link_short_name = self.shorteners.short_link_names.translate(name, False)
res.web_show_str = ("<strong>%s</strong> %s %s %s (source: %s, target: %s, class: %s)" %
(res.name, colorize_bg(res.channel_handle), res.role, res.link_short_name_popup,
res.source, res.target, res.link_class))
res.sdorg_str = ("%s %s %s %s (src: %s, tgt: %s)" %
(res.name, res.channel_handle, res.role, res.link_short_name,
res.source, res.target))
elif perf == 0x14:
# Performative: transfer [channel,handle] (id)
self.transfer_short_name = self.shorteners.short_data_names.translate(res.transfer_bare, customer=self)
showdat = "<a href=\"#%s_dump\">%s</a>" % (self.transfer_short_name, self.transfer_short_name)
spl = common.strings_of_proton_log(res.transfer_bare)
to_strip = "\"... (truncated)"
if spl.endswith(to_strip):
spl = spl[:-len(to_strip)]
res.web_show_str = "<strong>%s</strong> %s (%s) %s %s %s %s %s %s - %s bytes" % (
res.name, colorize_bg(res.channel_handle), res.delivery_id,
self.highlighted("settled", res.transfer_settled, common.color_of("presettled")),
self.highlighted("more", res.transfer_more, common.color_of("more")),
self.highlighted("resume", res.transfer_resume, common.color_of("aborted")),
self.highlighted("aborted", res.transfer_aborted, common.color_of("aborted")),
showdat, spl[-SEQUENCE_TRANSFER_SIZE:],
res.transfer_size)
res.sdorg_str = "%s %s (%s) %s (%s%s%s%s)\\n%s" % (
res.name, res.channel_handle, res.delivery_id, res.transfer_size,
self.unhighlighted(" settled", res.transfer_settled),
self.unhighlighted(" more", res.transfer_more),
self.unhighlighted(" resume", res.transfer_resume),
self.unhighlighted(" aborted", res.transfer_aborted),
spl[-SEQUENCE_TRANSFER_SIZE:])
def adverbl_link_to(self):
"""
:return: html link to the main adverbl data display for this line
"""
return "<a href=\"#%s\">%s</a>" % (self.fid, "%s%d_%s" %
(common.log_letter_of(self.index), self.instance, str(self.lineno)))
def __init__(self, _log_index, _instance, _lineno, _line, _comn, _router, opaque=None):
"""
Process a naked qpid-dispatch log line
A log line looks like this:
2018-07-20 10:58:40.179187 -0400 SERVER (trace) [2]:0 -> @begin(17) [next-outgoing-id=0, incoming-window=2147483647, outgoing-window=2147483647] (/home/chug/git/qpid-dispatch/src/server.c:106)
The process is:
1. If the line ends with a filename:fileline then strip that away
2. Peel off the leading time of day and put that into data.datetime.
Lines with no datetime are presumed start-of-epoch.
3. Find (SERVER) or (POLICY). If absent then raise to reject message.
4. If connection number in square brackets '[2]' is missing then raise.
5. Extract connection number; save in data.conn_num
6. Create decorated data.conn_id "A0_2"
7. Extract data.channel if present. Raise if malformed.
8. Create a web_show_str for lines that may not parse any further. Like policy lines.
9. Extract the direction arrows
The log line is now reduced to a described type:
@describedtypename(num) [key=val [, key=val ...]]
except for transfers that have the funky transfer data at end.
:param _log_index: The router prefix index 0 for A, 1 for B, ...
:param _instance The router instance
:param _lineno:
:param _line:
:param _comn:
:param _router:
"""
verbatim_module = None
if len(_comn.verbatim_include_list) > 0:
for modx in _comn.verbatim_include_list:
if _comn.module_key_in_line(modx, _line):
verbatim_module = modx
break
if not (_comn.module_key_in_line(self.server_trace_key, _line) or
_comn.module_key_in_line(self.protocol_trace_key, _line) or
(_comn.module_key_in_line(self.policy_trace_key, _line) and "lookup_user:" in _line) or # open (not begin, attach)
_comn.module_key_in_line(self.server_info_key, _line) or
_comn.module_key_in_line(self.router_ls_key, _line) or
verbatim_module is not None):
raise ValueError("Line is not a candidate for parsing")
self.index = _log_index # router prefix 0 for A, 1 for B
self.instance = _instance # router instance in log file
self.lineno = _lineno # log line number
self.comn = _comn
self.router = _router
self.opaque = opaque
self.prefixi = common.log_letter_of(self.index) + str(self.instance) # prefix+instance A0
self.fid = "f_" + self.prefixi + "_" + str(self.lineno) # frame id A0_100
self.shorteners = _comn.shorteners # name shorteners
self.line = _line # working line chopped, trimmed
self.data = LogLineData() # parsed line fact store
# strip optional trailing file:line field
self.line = self.line.rstrip()
hasFileLine = False
if self.line.endswith(')'):
idxOP = self.line.rfind('(')
idxColon = self.line.rfind(':')
if idxOP != -1 and idxColon != -1:
if idxColon > idxOP:
lNumStr = self.line[(idxColon + 1): (-1)]
try:
lnum = int(lNumStr)
hasFileLine = True
except:
pass
if hasFileLine:
self.line = self.line[:self.line.rfind('(')].rstrip()
# Handle optional timestamp
# This whole project is brain dead without a timestamp. Just sayin'.
self.datetime = None
try:
self.datetime = datetime.strptime(self.line[:26], '%Y-%m-%d %H:%M:%S.%f')
except:
# old routers flub the timestamp and don't print leading zero in uS time
# 2018-11-18 11:31:08.269 should be 2018-11-18 11:31:08.000269
td = self.line[:26]
parts = td.split('.')
us = parts[1]
parts_us = us.split(' ')
if len(parts_us[0]) < 6:
parts_us[0] = '0' * (6 - len(parts_us[0])) + parts_us[0]
parts[1] = ' '.join(parts_us)
td = '.'.join(parts)
try:
self.datetime = datetime.strptime(td[:26], '%Y-%m-%d %H:%M:%S.%f')
except:
self.datetime = datetime(1970, 1, 1)
# Apply time-of-day filters
if self.datetime is not None:
if self.comn.args.time_start is not None:
if self.datetime < self.comn.args.time_start:
raise ValueError("Line too early outside time-of-day limits")
if self.comn.args.time_end is not None:
if self.datetime > self.comn.args.time_end:
raise ValueError("Line too late outside time-of-day limits")
# Pull out scraper literal logs
if verbatim_module is not None:
sti = self.line.find(verbatim_module)
if sti > 0:
# strip datetime and show literal string
sti += len(verbatim_module)
self.data.is_scraper = True
self.data.web_show_str = ("<strong>%s</strong> %s" % (verbatim_module, common.html_escape(self.line[sti:])))
stcp = self.line[sti:].find(')') # close paren after log level
if stcp < 0:
stcp = 0
self.data.sdorg_str = self.line[sti + stcp + 1:]
return
else:
assert False # verbatim module was found only moments ago...
# extract connection number
sti = self.line.find(self.server_trace_key)
if sti < 0:
sti = self.line.find(self.protocol_trace_key)
if sti < 0:
sti = self.line.find(self.policy_trace_key)
if sti < 0:
sti = self.line.find(self.server_info_key)
if sti < 0:
sti = self.line.find(self.router_ls_key)
if sti < 0:
raise ValueError("Log keyword/level not found in line %s" % (self.line))
else:
self.line = self.line[sti + len(self.router_ls_key):]
self.data.is_router_ls = True
# this has no relationship to AMQP log lines
return
else:
self.line = self.line[sti + len(self.server_info_key):]
self.data.is_server_info = True
else:
self.line = self.line[sti + len(self.policy_trace_key):]
self.data.is_policy_trace = True
else:
self.line = self.line[sti + len(self.protocol_trace_key):]
else:
self.line = self.line[sti + len(self.server_trace_key):]
ste = self.line.find(']')
if ste < 0:
print("Failed to parse line ", _lineno, " : ", _line)
traceback.print_exc()
raise ValueError("'%s' not found in line %s" % ("]", self.line))
self.data.conn_num = self.line[:ste]
if self.data.conn_num.startswith("C"):
self.data.conn_num = self.data.conn_num[1:]
self.line = self.line[ste + 1:]
# create decorated connection id
self.data.conn_id = self.prefixi + "_" + self.data.conn_num
# get the session (channel) number
if self.line.startswith(':'):
self.line = self.line[1:]
if self.line.startswith(self.proton_frame_key):
self.line = self.line[len(self.proton_frame_key):]
sti = self.line.find(' ')
if sti < 0:
raise ValueError("space not found after channel number at head of line %s" % (self.line))
if sti > 0:
self.data.channel = self.line[:sti]
self.line = self.line[sti + 1:]
self.line = self.line.lstrip()
# cover for traces that don't get any better
self.data.web_show_str = ("<strong>%s</strong>" % self.line)
# policy lines have no direction and described type fields
if self.data.is_policy_trace or self.data.is_server_info:
return
# direction
if self.line.startswith('<') or self.line.startswith('-'):
self.data.direction = self.line[:2]
self.line = self.line[3:]
self.data.web_show_str = ("<strong>%s</strong>" % self.line)
# The log line is now reduced to a described type:
# @describedtypename(num) [key=val [, key=val ...]]
# extract descriptor name
dname = self.line.split()[0]
self.line = self.line[(len(dname) + 1):]
# Dispose of the transfer data
if dname == self.transfer_key:
# Look for the '] (NNN) "' that separates the described type fields
# from the '(size) "data"'. Stick the required '(size) data' into
# data.transfer_data and delete it from the line.
rz = re.compile(r'\] \(\d+\) \"').search(self.line)
# aborted transfers may or may not have size/data in the log line
if rz is not None and len(rz.regs) > 0:
splitSt, splitTo = rz.regs[0]
self.data.transfer_size = self.line[splitSt + 3: splitTo - 3]
self.data.transfer_data = self.line[splitTo - 1:] # discard (NNN) size field
self.line = self.line[: splitSt + 1]
# try to isolate the bare message
sti = self.data.transfer_data.find(r"\x00Ss")
if sti > 0:
self.data.transfer_hdr_annos = self.data.transfer_data[:sti]
self.data.transfer_bare = self.data.transfer_data[sti:]
else:
self.data.transfer_hdr_annos = ''
self.data.transfer_bare = self.data.transfer_data
else:
self.data.transfer_size = "0"
self.data.transfer_data = "(none)"
if DescribedType.is_dtype_name(dname):
self.data.described_type.parse_dtype_line(dname, self.line)
# data fron incoming line is now parsed out into facts in .data
# Now cook the data to get useful displays
self.extract_facts()
def parse_log_file(fn, log_index, comn):
"""
Given a file name, return an array of Routers that hold the parsed lines.
Lines that don't parse are identified on stderr and then discarded.
:param fn: file name
:param log_index: router id 0 for 'A', 1 for 'B', ...
:param comn: common data
:return: list of Routers
"""
instance = 0
lineno = 0
search_for_in_progress = True
rtrs = []
rtr = None
key1 = "SERVER (trace) [" # AMQP traffic
key2 = "SERVER (info) Container Name:" # Normal 'router is starting' restart discovery line
key3 = "ROUTER_LS (info)" # a log line placed in separate pool of lines
keys = [key1, key3]
key4 = "ROUTER (info) Version:" # router version line
key5 = "ROUTER (info) Router started in " # router mode
with open(fn, 'r') as infile:
for line in infile:
if search_for_in_progress:
# What if the log file has no record of the router starting?
# This is an in_progress router and it is a pre-existing router instance
# and not one found by restart discovery.
# Any key or AMQP line indicates a router in-progress
if any(s in line for s in keys) or ("[" in line and "]" in line):
assert rtr is None
rtr = router.Router(fn, log_index, instance)
rtrs.append(rtr)
search_for_in_progress = False
rtr.restart_rec = router.RestartRecord(rtr, line, lineno + 1)
lineno += 1
verbatim_module = None
if len(comn.verbatim_include_list) > 0:
for modx in comn.verbatim_include_list:
if comn.module_key_in_line(modx, line):
verbatim_module = modx
break
if key2 in line:
# This line closes the current router, if any, and opens a new one
if rtr is not None:
instance += 1
rtr = router.Router(fn, log_index, instance)
rtrs.append(rtr)
rtr.restart_rec = router.RestartRecord(rtr, line, lineno)
search_for_in_progress = False
rtr.container_name = line[(line.find(key2) + len(key2)):].strip().split()[0]
elif key3 in line:
pl = None
try:
pl = ParsedLogLine(log_index, instance, lineno, line, comn, rtr)
except ValueError as ve:
pass
except Exception as e:
# t, v, tb = sys.exc_info()
if hasattr(e, 'message'):
traceback.print_exc()
sys.stderr.write("Failed to parse file '%s', line %d : %s. Analysis continuing...\n" % (fn, lineno, e.message))
else:
traceback.print_exc()
sys.stderr.write("Failed to parse file '%s', line %d : %s. Analysis continuing...\n" % (fn, lineno, e))
if pl is not None:
if pl.data.is_router_ls:
rtr.router_ls.append(pl)
elif key4 in line:
rtr.version = line[(line.find(key4) + len(key4)):].strip().split()[0]
elif key5 in line:
rtr.mode = line[(line.find(key5) + len(key5)):].strip().split()[0].lower()
elif verbatim_module is not None:
pl = ParsedLogLine(log_index, instance, lineno, line, comn, rtr)
rtr.lines.append(pl)
elif "[" in line and "]" in line:
try:
do_this = True if not hasattr(comn.args, 'skip_all_data') else not comn.args.skip_all_data
if not do_this:
# not indexing data. maybe do this line anyway
do_this = not any(s in line for s in [' @transfer', ' @disposition', ' @flow', 'EMPTY FRAME'])
if do_this:
pl = ParsedLogLine(log_index, instance, lineno, line, comn, rtr)
if pl is not None:
rtr.lines.append(pl)
else:
comn.data_skipped += 1
except ValueError as ve:
pass
except Exception as e:
# t, v, tb = sys.exc_info()
if hasattr(e, 'message'):
traceback.print_exc()
sys.stderr.write("Failed to parse file '%s', line %d : %s. Analysis continuing...\n" % (fn, lineno, e.message))
else:
traceback.print_exc()
sys.stderr.write("Failed to parse file '%s', line %d : %s. Analysis continuing...\n" % (fn, lineno, e))
# raise t, v, tb
else:
# ignore this log line
pass
return rtrs
if __name__ == "__main__":
class dummy_args():
skip_all_data = False
skip_detail = False
skip_msg_progress = False
split = False
time_start = None
time_end = None
print("Line-by-line split test")
try:
for line in td.TestData().data():
if "transfer" not in line:
print(proton_split(line))
else:
pass # splitter does not split transfers
pass
except:
traceback.print_exc(file=sys.stdout)
pass
print("Canned data parse test")
data = td.TestData().data()
log_index = 0 # from file for router A
instance = 0 # all from router instance 0
comn = common.Common()
comn.args = dummy_args()
try:
for i in range(len(data)):
temp = ParsedLogLine(log_index, instance, i, data[i], comn, None)
print(temp.datetime, temp.data.conn_id, temp.data.direction, temp.data.web_show_str)
pass
except:
traceback.print_exc(file=sys.stdout)
pass
comn.shorteners.short_data_names.sort_customers()
print("Read two-instance file test")
comn2 = common.Common()
comn2.args = dummy_args()
routers = parse_log_file('test_data/A-two-instances.log', 0, comn2)
if len(routers) != 2:
print("ERROR: Expected two router instances in log file")
t_b4_0 = datetime.strptime('2018-10-15 10:57:32.151673', '%Y-%m-%d %H:%M:%S.%f')
t_in_0 = datetime.strptime('2018-10-15 10:57:32.338183', '%Y-%m-%d %H:%M:%S.%f')
t_in_1 = datetime.strptime('2018-10-15 10:59:07.584498', '%Y-%m-%d %H:%M:%S.%f')
t_af_1 = datetime.strptime('2019-10-15 10:59:07.584498', '%Y-%m-%d %H:%M:%S.%f')
rtr, idx = router.which_router_tod(routers, t_b4_0)
assert rtr is routers[0] and idx == 0
rtr, idx = router.which_router_tod(routers, t_in_0)
assert rtr is routers[0] and idx == 0
rtr, idx = router.which_router_tod(routers, t_in_1)
assert rtr is routers[1] and idx == 1
rtr, idx = router.which_router_tod(routers, t_af_1)
assert rtr is routers[1] and idx == 1
pass