OverviewFilesCommitsIssuesDocsReleasesPeers

opencog-cogutil / scripts / util / sort-log.py

172 lines · python ref:
#!/usr/bin/env python3

# Sort a log according to some order (chronological or thread
# cohesiveness for now).

import re
import sys
import datetime
import argparse
import functools

def datetime_from_str(time_str):
    """Return <datetime.datetime() instance> for the given
    datetime string given in OpenCog's date time log format.

    >>> datetime_from_str("2009-12-25 13:05:14:453")
    datetime.datetime(2009, 12, 25, 13, 5, 14, 453000)
    """
    fmt = "%Y-%m-%d %H:%M:%S:%f"
    return datetime.datetime.strptime(time_str, fmt)

def compare(x, y):
    if x < y:
        return -1
    elif x > y:
        return 1
    else:
        return 0

def chrono_compare(x, y):
    """Compare keys x and y so that

    x = (xtln, xdt, xln)
    y = (ytln, ydt, yln)

    ignoring thread line number, that is compare

    (xdt, xln) and (ydt, xln)
    """
    xtln, xdt, xln = x
    ytln, ydt, yln = y

    return compare((xdt, xln), (ydt, yln))

def thread_compare(x, y):
    """Compare keys x and y so that

    x = (xtln, xdt, xln)
    y = (ytln, ydt, yln)

    1. If xtln or ytln is None, or xtln == ytln then ignore the first
    two fields, that is compare with line number.

    2. If xtln and ytln are not None, and xtln != ytln then compare
    with thread line number and line number.

    """
    xtln, xdt, xln = x
    ytln, ydt, yln = y

    if xtln and ytln and xtln != ytln:
        return compare((xtln, xln), (ytln, yln))
    else:
        return compare(xln, yln)

def ln_compare(x, y):
    """Compare keys x and y

    x = (xtln, xdt, xln)
    y = (ytln, ydt, yln)

    by line number.
    """
    xtln, xdt, xln = x
    ytln, ydt, yln = y

    return compare(xln, yln)

def thread_chrono_compare(x, y):
    """Compare keys x and y so that

    x = (xtln, xdt, xln)
    y = (ytln, ydt, yln)

    1. If xtln or ytln is None, or xtln == ytln then ignore the first
    field, that is compare with the remaining fields datetime and line
    number.

    2. If xtln and ytln are not None, and xtln != ytln then compare
    the whole triplet with the standard order.

    """
    xtln, xdt, xln = x
    ytln, ydt, yln = y

    if xtln and ytln and xtln != ytln:
        return compare(x, y)
    else:
        return chrono_compare(x, y)

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description='Sort the given log according to a given order, such as chronological, thread cohesive, etc.')
    parser.add_argument('logfile', help='Log file to sort')
    parser.add_argument('-c', '--chrono', action='store_true', default=True,
                        help='Sort chronologically. Indeed, such order can be broken if the logger is asynchronous.')
    parser.add_argument('-t', '--thread', action='store_true', default=False,
                        help='Sort such that messages from the same thread are clumped together.')
    parser.add_argument('-o', '--output',
                        help='Output file. If unused stdout is used instead')
    args = parser.parse_args()

    timestamp_re = r'\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}:\d{3})\]'
    timestamp_prog = re.compile(timestamp_re)
    thread_re = r'.*\[(thread-\d+)\]'
    thread_prog = re.compile(thread_re)

    # Output file
    of = open(args.output, "w") if args.output else sys.stdout
    
    # 1. Put the text in a dict (tln, dt, ln) -> message
    #
    #    where
    #
    #    - tln is the first line number of its corresponding thread
    #    - dt is the corresponding datetime
    #    - ln is the line number of the first line of the message
    # 
    # 2. Map thread to its first line number
    key2txt = {}
    tln, dt, ln = None, None, None
    line_num = 0
    t2ln = {}
    for l in open(args.logfile):
        # Parse timestamp, thread and fill the key2txt entry
        timestamp_m = timestamp_prog.match(l)
        thread_m = thread_prog.match(l)
        if timestamp_m:
            # Determine message line number
            ln = line_num
            # Determine thread line number, if any
            if thread_m:
                thread = thread_m.group(1)
                if thread not in t2ln:
                    t2ln[thread] = ln
                tln = t2ln[thread]    
            else:
                tln = None
            # Determine datetime
            dt = datetime_from_str(timestamp_m.group(1))
            key2txt[(tln, dt, ln)] = l
        elif dt:
            key2txt[(tln, dt, ln)] += l
        else:
            # Corner case, it seems the log's first line can be without
            # timestamp
            of.write(l)
        line_num += 1

    # Determine the right compare function
    if args.chrono and args.thread:
        cmp = thread_chrono_compare
    elif args.chrono:
        cmp = chrono_compare
    elif args.thread:
        cmp = thread_compare
    else:
        cmp = ln_compare

    # Write sorted log to output
    for tln, dt, ln in sorted(key2txt.keys(), key=functools.cmp_to_key(cmp)):
        of.write(key2txt[(tln, dt, ln)])