Add utility to combine runs of lines in log files. (#317)
Hopefully satisfies #193. Change-Id: I427d763aeca2322b05ed88b42fd4a5f0446a654bdeinit
parent
d18c2f23d3
commit
498faf93e1
|
@ -0,0 +1,119 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# This function is the only OpenOCD-specific part of this script.
|
||||||
|
def make_canonical(line):
|
||||||
|
# Remove the line number and time stamp.
|
||||||
|
parts = line.split(None, 3)
|
||||||
|
if len(parts) > 3:
|
||||||
|
return "%s - - %s" % (parts[0], parts[3])
|
||||||
|
else:
|
||||||
|
return line
|
||||||
|
|
||||||
|
def buf_startswith(buf, sequence):
|
||||||
|
if len(buf) < len(sequence):
|
||||||
|
return False
|
||||||
|
for i, entry in enumerate(sequence):
|
||||||
|
if entry[1] != buf[i][1]:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def shorten_buffer(outfd, buf, current_repetition):
|
||||||
|
"""Do something to the buffer to make it shorter. If we can't compress
|
||||||
|
anything, then print out the first line and remove it."""
|
||||||
|
length_before = len(buf)
|
||||||
|
|
||||||
|
if current_repetition:
|
||||||
|
while buf_startswith(buf, current_repetition[0]):
|
||||||
|
del buf[:len(current_repetition[0])]
|
||||||
|
current_repetition[1] += 1
|
||||||
|
if len(buf) < length_before:
|
||||||
|
return current_repetition
|
||||||
|
outfd.write("## The following %d lines repeat %d times:\n" % (
|
||||||
|
len(current_repetition[0]), current_repetition[1]))
|
||||||
|
for entry in current_repetition[0]:
|
||||||
|
outfd.write("# %s" % entry[1])
|
||||||
|
|
||||||
|
# Look for repeated sequences...
|
||||||
|
repetitions = []
|
||||||
|
for length in range(1, len(buf)/2):
|
||||||
|
# Is there a repeating sequence of `length` lines?
|
||||||
|
matched_lines = 0
|
||||||
|
for i, entry in enumerate(buf[length:]):
|
||||||
|
if entry[1] == buf[i % length][1]:
|
||||||
|
matched_lines += 1
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
if matched_lines >= length:
|
||||||
|
repetitions.append((matched_lines + length, length))
|
||||||
|
|
||||||
|
if repetitions:
|
||||||
|
repetitions.sort(key=lambda entry: (entry[0] * (entry[1] / entry[0]), -entry[1]))
|
||||||
|
matched_lines, length = repetitions[-1]
|
||||||
|
repeated = matched_lines / length
|
||||||
|
if repeated * length >= 3:
|
||||||
|
sequence = buf[:length]
|
||||||
|
del buf[:repeated * length]
|
||||||
|
|
||||||
|
if matched_lines == length_before:
|
||||||
|
# Could be continued...
|
||||||
|
return [sequence, repeated]
|
||||||
|
|
||||||
|
else:
|
||||||
|
outfd.write("## The following %d lines repeat %d times:\n" %
|
||||||
|
(length, repeated))
|
||||||
|
for entry in sequence:
|
||||||
|
outfd.write("# %s" % entry[1])
|
||||||
|
return None
|
||||||
|
|
||||||
|
if len(buf) >= length_before:
|
||||||
|
line, _ = buf[0]
|
||||||
|
outfd.write(line)
|
||||||
|
buf.pop(0)
|
||||||
|
|
||||||
|
if length_before <= len(buf):
|
||||||
|
print "Buffer:"
|
||||||
|
for entry in buf:
|
||||||
|
print "%r" % entry[0]
|
||||||
|
assert False
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def compress_log(infd, outfd, window):
|
||||||
|
"""Compress log by finding repeated runs of lines. Runs in O(lines *
|
||||||
|
window**2), which can probably be improved."""
|
||||||
|
# Contains line, canonical tuples
|
||||||
|
buf = []
|
||||||
|
current_repetition = None
|
||||||
|
|
||||||
|
for line in infd:
|
||||||
|
buf.append((line, make_canonical(line)))
|
||||||
|
if len(buf) > window:
|
||||||
|
current_repetition = shorten_buffer(outfd, buf, current_repetition)
|
||||||
|
|
||||||
|
while len(buf) > 0:
|
||||||
|
current_repetition = shorten_buffer(outfd, buf, current_repetition)
|
||||||
|
|
||||||
|
def main(args):
|
||||||
|
import argparse
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description='Combine repeated OpenOCD debug output lines. This is '
|
||||||
|
'very helpful when looking at verbose log files where e.g. target '
|
||||||
|
'polling is repeated over and over.',
|
||||||
|
epilog='If no files are specified, read standard input.',
|
||||||
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||||
|
parser.add_argument('file', nargs='*', help='input file')
|
||||||
|
parser.add_argument('-o', '--output', help='output file', default=sys.stdout)
|
||||||
|
parser.add_argument('-w', '--window', type=int, default=100,
|
||||||
|
help='number of lines to consider when looking for repetitions')
|
||||||
|
args = parser.parse_args(args)
|
||||||
|
|
||||||
|
if args.file:
|
||||||
|
for f in args.file:
|
||||||
|
compress_log(open(f, "r"), args.output, args.window)
|
||||||
|
else:
|
||||||
|
compress_log(sys.stdin, args.output, args.window)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.exit(main(sys.argv[1:]))
|
Loading…
Reference in New Issue