#!/usr/bin/env python
"""
Re-format NetLogger log lines.
"""
__author__ = "Dan Gunter <dkgunter@lbl.gov>"
__rcsid__ = "$Id: nl_view 802 2008-06-06 18:15:21Z dang $"

import optparse
import random
import sys
from netlogger.parsers.base import NLFastParser
	
class TinyIDMapper:
    """Map incoming identifiers to much shorter ones.
    """
    N = 4
    BASE = 'a'
    def __init__(self):
        self.t = { }
        random.seed(self.N)

    def shrink(self, guid):
        try:
            tiny_guid = self.t[guid]
        except KeyError:
            self.t[guid] = tiny_guid = self._tiny()
        return tiny_guid

    def _tiny(self):
        n, a = range(self.N), ord(self.BASE)
        while 1:
            x = [chr(a + random.randint(0,25)) for i in n] 
            s = ''.join(x)
            if not self.t.has_key(s):
                break
        return s

def run(inf, outf, format=None, options=None, header=None):
    deltas = options.deltas or options.cumdeltas
    cum_deltas = options.cumdeltas
    ns_len = len(options.ns)
    parser = NLFastParser(inf, parse_date=deltas, strip_quotes=False)
    if deltas:
        last_ts = -1
    if options.ids and not options.all_attrs:
        # don't duplicate attributes that will be displayed
        # anyways because they end in '.id'
        options.attrs = filter(lambda a: not a.endswith('.id'),
                               options.attrs)
    if options.tinyid:
        tinyids = TinyIDMapper()
    else:
        tinyids = None
    if header:
        outf.write(header)
        outf.write("\n")
    for d in parser:
        if not d: continue
        ts = d['ts']
        if deltas:
            if last_ts == -1:
                cur_ts = 0
            else:
                cur_ts = ts - last_ts
            if last_ts == -1 or not cum_deltas:
                last_ts = ts
            d['ts'] = cur_ts
        else:
            d['ts'] = ts[:10] + " " + ts[11:]
        if tinyids:
            for key in d.keys():
                if key == 'guid' or key.endswith(".id"):
                    d[key] = tinyids.shrink(d[key])
        if not d.has_key('guid') and options.add_guid:
            d['guid'] = '?'
        if options.add_level and not d.has_key('level'):
            d['level'] = 'INFO'
        if ns_len:
            e = d['event']
            if e.startswith(options.ns):
                d['event'] = e[ns_len:]
        outf.write(format % d)
        if options.all_attrs:
            first = True
            for key in d.keys():
                if key in ('ts', 'event'): continue
                if key == 'guid' and options.add_guid: continue
                if key == 'level' and options.add_level: continue
                if not first:
                    outf.write(' ')
                else:
                    first = False
                outf.write("%s=%s" % (key, d[key]))
        else:
            first = True
            if options.ids:
                for key in d.keys():
                    if not first:
                        outf.write(' ')
                    else:
                        first = False
                    if key.endswith(".id"):
                        outf.write("%s=%s" % (key, d[key]))
            for attr in options.attrs:
                if not first:
                    outf.write(' ')
                else:
                    first = False
                if d.has_key(attr):
                    outf.write("%s=%s" % (attr, d[attr]))
        outf.write("\n")
        
def main():
    usage = "%prog [options] [files..]"
    desc = ' '.join(__doc__.split('\n'))
    parser = optparse.OptionParser(usage=usage, version="%prog 0.1",
                                  description=desc)
    parser.add_option('-a','--attr', action="append", dest="attrs",
                      metavar="ATTR", default=[],                       
                      help="add attribute ATTR to output line, repeatable") 
    parser.add_option('-A', '--all', action="store_true", dest="all_attrs",
                      default=False,
                      help="add all attributes to output line")
    parser.add_option('-c', '--cum-delta', action="store_true", dest="cumdeltas",
                      default=False,
                      help="show times as deltas since first (defalt=%default)")
    parser.add_option('-d', '--delta', action="store_true", dest="deltas",
                      default=False,
                      help="show times as deltas from previous (defalt=%default)")
    parser.add_option('-D', '--delimiter', dest="delim", default=" ",
                      help="column delimiter (default='%default')")
    parser.add_option('-g', '--guid',
                      action="store_true", dest="add_guid",
                      help="add 'guid' attribute")
    parser.add_option('-H', '--header', action="store_true", dest="hdr",
                      help="add header row (default=%default)", default=False)
    parser.add_option('-i', '--host',
                      action="store_true", dest="add_host",
                      help="add 'host' attribute")
    parser.add_option('-I', '--identifiers', action="store_true", dest="ids",
                      help="add any attribute ending in '.id'")
    parser.add_option('-l', '--level', dest="add_level", action="store_true",
                      help="add 'level' attribute'")
    parser.add_option('-m', '',
                      action='store_true', dest="add_msg",
                      default=False,
                      help="add 'msg' attribute") 
    parser.add_option('-n', '--namespace', default="",
                      metavar="PREFIX", dest="ns",
                      help="strip namespace PREFIX if found")
    parser.add_option('-s', '--status',
                      action='store_true', dest="add_status",
                      default=False,
                      help="add 'status' attribute")
    parser.add_option('-t', '--tiny-id',
                      action='store_true', dest="tinyid",
                      default=False,
                      help="replace *.id and guid values with shorter id's, "
                      "like tinyurl")
    parser.add_option('-w', '--width', metavar="NUM",
                      action='store', type='int', dest='event_width',
                      default=40,
                      help="set event column width to NUM (default=%default)")
    parser.add_option('-x', '',
                      action="store_true", dest="noecho",
                      default=False,
                      help="ignore non-NetLogger lines")
    options, args= parser.parse_args()
    if options.tinyid and not (options.add_guid or options.ids):
        parser.error("option -t/--tiny-id given with no id's to reduce, "
                     "add -g/--guid or -I/--identifiers")
    if options.deltas:
        ts_fmt = "%(ts)11.06lf"
        ts_hdr = "%-11s"
    elif options.cumdeltas:
        ts_fmt = "%(ts)9.06lf"
        ts_hdr = "%-9s"
    else:
        ts_fmt = "%(ts)s"
        ts_hdr = "%-27s"
    format_bits = [ts_fmt, "%%(event)-%ds" % options.event_width]
    header_bits = [ts_hdr % 'time', ('%%-%ds' % options.event_width) % 'event']
    if options.add_guid:
        if options.tinyid:
            format_bits.insert(1,"%%(guid)-%ds" % TinyIDMapper.N)
            header_bits.insert(1,("%%-%ds" % TinyIDMapper.N) % 'guid')
        else:
            format_bits.insert(1,"%(guid)-36s")
            header_bits.insert(1,"%-36s" % 'guid')
    if options.add_level:
        format_bits.insert(1, "%(level)-8s")
        header_bits.insert(1, "%-8s" % 'level')
    for a in ('host', 'status', 'msg'):
        if getattr(options, 'add_' + a):
            options.attrs.append(a)
    if len(args) == 0:
        args = [None]
    fmt = options.delim.join(format_bits)
    if options.hdr:
        hdr = options.delim.join(header_bits)
    else:
        hdr = None
    if options.attrs or options.all_attrs:
        fmt += options.delim
        if options.hdr:
            hdr += options.delim + "attrs"
    outf = sys.stdout
    for a in args:
        if a is None:
            infile = sys.stdin
        else:
            infile = file(a)
        run(infile, outf, format=fmt, header=hdr, options=options)
    
if __name__ == "__main__": 
    main()
