blob: 9272499c682b70f67f9a0074f89718adb29597ef [file] [log] [blame]
#!/usr/bin/env python3
#
# 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 typing import List
import optparse
import fileinput
import json
import re
import sys
"""
This program accepts input for a modified Scraper and produces output to create sequence diagrams
at sequencediagram.org.
"""
IDX_DATETIME = 0
IDX_NAME_LEFT = 1
IDX_DIR = 2
IDX_NAME_RIGHT = 3
IDX_PERFORMATIVE = 4
IDX_ROUTER_LINE = 5
# Exactly how to predict how much 'space' asynchronous messages take is vague.
MAGIC_SPACE_NUMBER = 1 # tested with entryspacing 0.1
class log_record:
def __init__(self, index, line):
# print("DEBUG input line: ", index, line)
dateandtime, name_left, direction, name_right, perf, router_line, dummy = line.split('|')
self.dateandtime = dateandtime.strip()
self.time = self.dateandtime.split(' ')[1]
self.index = index
self.name_left = name_left.strip()
self.direction = direction.strip()
self.name_right = name_right.strip()
self.performative = perf.strip()
self.router_line = router_line.strip() # diag
self.peer_log_rec = None
dir_out = (direction == '->') # name_left -> name_right
self.sentby = name_left.strip() if dir_out else name_right.strip()
self.rcvdby = name_right.strip() if dir_out else name_left.strip()
def absorb_peer_rec(self, peer_rec):
'''
Given peer_rec, see if it is the receiving side of
this log_rec being sent. If so then cross-link the
records and return true.
:param peer_rec:
:return:
'''
valid_peer = self.name_left == peer_rec.name_right and \
self.name_right == peer_rec.name_left and \
self.direction != peer_rec.direction and \
self.performative.startswith(peer_rec.performative) and \
self.peer_log_rec is None and \
peer_rec.peer_log_rec is None
if not valid_peer:
return False
self.peer_log_rec = peer_rec
peer_rec.peer_log_rec = self
return True
def show_for_sdorg(self, showtime):
# A single sd.org vector consumes one or two log lines.
# One if from an external actor.
# Two if a message goes between routers.
# Leave space where the message is supposed to land in the receiving routers
if self.peer_log_rec is None:
if showtime:
print("%s->%s:%s %s" % (self.sentby, self.rcvdby, self.time, self.performative))
else:
print("%s->%s:%s" % (self.sentby, self.rcvdby, self.performative))
else:
if self.peer_log_rec.index > self.index:
linediff = int(self.peer_log_rec.index) - int(self.index)
space = -MAGIC_SPACE_NUMBER * (linediff - 1)
if showtime:
print("%s->(%s)%s:%s %s" % (self.sentby, str(linediff), self.rcvdby, self.time, self.performative))
else:
print("%s->(%s)%s:%s" % (self.sentby, str(linediff), self.rcvdby, self.performative))
print("space %s" % (str(space)))
else:
print("space %d" % MAGIC_SPACE_NUMBER)
def sender_receiver(self):
# Return sender receiver
return self.sentby, self.rcvdby
def diag_dump(self):
cmn = ("index: %d, dateandtime: %s, sentby: %s, rcvdby: %s, performative: %s, router_line: %s" %
(self.dateandtime, self.index, self.sentby, self.rcvdby, self.performative, self.router_line))
if self.peer_log_rec is None:
print(cmn)
else:
print("%s, PEER MATCH peer_index: %s, peer_router_log_line: %s" %
(cmn, self.peer_log_rec.index, self.peer_log_rec.router_line))
def split_log_file(filename):
'''
Given a filename, read the file into an array of lines
:param filename:
:return:
'''
if filename == "STDIN" or filename == "" or filename == "-":
log = sys.stdin
log_lines = log.read().split("\n")
else:
log = open(filename, 'r')
log_lines = log.read().split("\n")
log.close()
return log_lines
def match_logline_pairs(log_recs):
'''
Given a log line that might be the source of a message to another router,
search the list of remaining log lines and try to find the router that received
this message. Then hook them together.
The cut of Scraper output presented as input to this program may not be complete.
Messages for which there is no 'match' are drawn as horizontal lines.
:param log_lines:
:return:
'''
for idx in range(len(log_recs) - 2):
for idx2 in range(idx + 1, len(log_recs) - 1):
if log_recs[idx].absorb_peer_rec(log_recs[idx2]):
break
if __name__ == "__main__":
parser = optparse.OptionParser(usage="%prog [options]",
description="cooks a scraper log snippet into sequence diagram source")
parser.add_option("-f", "--filename", action="append", help="logfile to use or - for stdin")
parser.add_option('--timestamp', '-t',
action='store_true',
help='Include HH:MM:SS.ssssss in output')
(opts, args) = parser.parse_args()
if not opts.filename:
opts.filename = ["-"]
log_recs = []
for logfile in opts.filename:
# print("Parsing: %s" % logfile)
log_lines = split_log_file(logfile)
if len(log_lines) >= 2:
index = 0
for logline in log_lines:
if len(logline) > 0:
log_recs.append(log_record(index, logline))
index += 1
match_logline_pairs(log_recs)
# print senders and receivers marking as actor or participant
names = set()
for log_rec in log_recs:
sndr, rcvr = log_rec.sender_receiver()
if sndr is not None:
names.add(sndr)
if rcvr is not None:
names.add(rcvr)
for name in names:
if name.startswith("peer"):
print("actor %s" % name)
else:
print("participant %s" % name)
print()
# process the list of records
for log_rec in log_recs:
log_rec.show_for_sdorg(opts.timestamp)
# diag dump
#print("\nDiag dump")
# for log_rec in log_recs:
# log_rec.diag_dump()
else:
print("Log file has fewer than two lines. I give up.")
print()