#!/usr/bin/python

# Script to print some interesting stats about cache based on the
# isi.cache.stats sysctls

import errno
import time
import isi.misc as misc
import getopt
import sys

DOMS = [
    'l3_meta',
    'l3_data',
    'l2_meta',
    'l2_data',
    'l1_meta',
    'l1_data',
]

STATS = [
    'aread.start',
    'aread.hit',
    'aread.miss',
    'aread.wait',
    'read.start',
    'read.hit',
    'read.miss',
    'read.wait',
    'prefetch.start',
    'prefetch.hit',
]

MISSES_L1 = 'prefetch_misses_l1'
MISSES_L2 = 'prefetch_misses_l2'

empty = {}
for dom in DOMS:
    domd = {}
    for stat in STATS:
        domd[stat] = 0
    empty[dom] = domd
    empty[MISSES_L1] = 0
    empty[MISSES_L2] = 0

def gather():
    data = {}
    for dom in DOMS:
        domd = {}
        for stat in STATS:
            ctl = 'isi.cache.stats.' + dom + '.' + stat
            try:
                val = misc.sysctlbyname(ctl)
                domd[stat] = val
            except:
                # This domain doesn't have that stat.
                pass
        data[dom] = domd

    for st in [MISSES_L1, MISSES_L2]:
        ctl = 'isi.cache.stats.' + st
        val = misc.sysctlbyname(ctl)
        data[st] = val

    return data

def short_amt(blocks):
    names = ['B', 'K', 'M', 'G', 'T', 'P', 'E']

    amt = blocks * 8192.0
    unit = 0
    while amt > 999:
        amt /= 1024
        unit += 1

    if amt < 5:
        str = '%1.1f%s' % (amt, names[unit])
    else:
        str = '%d%s' % (round(amt, 0), names[unit])
    return str

def display_simple(new, old):
    first = True
    out = ''
    for dom in sorted(new):
        if dom == MISSES_L1 or dom == MISSES_L2:
            break

        newd = new[dom]
        oldd = old[dom]

        if 'aread.start' in newd:
            tot = newd['aread.start'] - oldd['aread.start']
            ahit = 0
            for rs in ['aread.hit']:
                ahit += newd[rs] - oldd[rs]
            if tot == 0:
                apct = 0
            else:
                apct = (float(ahit) / float(tot)) * 100.0
            aread_str = 'a %4s %3.0f%% ' % (short_amt(ahit), apct)
        else:
            aread_str = ''

        tot = newd['read.start'] - oldd['read.start']
        rhit = 0
        for rs in ['read.hit', 'prefetch.hit']:
            rhit += newd[rs] - oldd[rs]

        if tot == 0:
            rpct = 0
        else:
            rpct = (float(rhit) / float(tot)) * 100.0

        tot = newd['prefetch.start'] - oldd['prefetch.start']
        phit = newd['prefetch.hit'] - oldd['prefetch.hit']

        if tot == 0:
            ppct = 0
        else:
            ppct = (float(phit) / float(tot)) * 100.0

        out += ('%s%s: %sr %4s %3.0f%% p %4s %3.0f%%'
                % ('' if first else ', ', dom,
                   aread_str,
                   short_amt(rhit), rpct,
                   short_amt(phit), ppct))
        first = False

    print out

def display_verbose(new, old):
    pref_l1 = 0
    pref_l2 = 0

    for dom in sorted(new):
        if dom == MISSES_L1 or dom == MISSES_L2:
            break

        newd = new[dom]
        oldd = old[dom]

        print '%s:' % (dom)

        if 'aread.start' in newd:
            print '\tasync read (8K blocks):'
            tot = newd['aread.start'] - oldd['aread.start']
            for rs in ['aread.start', 'aread.hit', 'aread.miss', 'aread.wait']:
                st = newd[rs] - oldd[rs]
                if tot == 0:
                    pct = 0
                else:
                    pct = float(st) / float(tot)
                print('\t\t%-24s %9d / %5.1f%%'
                      % (('%s:' % (rs,)), st, pct * 100.0))
            print

        print '\tread (8K blocks):'
        tot = newd['read.start'] - oldd['read.start']
        for rs in ['read.start', 'read.hit', 'read.miss', 'read.wait',
                   'prefetch.hit']:
            st = newd[rs] - oldd[rs]
            if tot == 0:
                pct = 0
            else:
                pct = float(st) / float(tot)
            print('\t\t%-24s %9d / %5.1f%%'
                  % (('%s:' % (rs,)), st, pct * 100.0))
        print

        print '\tprefetch (8K blocks):'
        tot = newd['prefetch.start'] - oldd['prefetch.start']

        if dom[:2] == 'l1':
            pref_l1 += tot
        else:
            pref_l2 += tot

        for rs in ['prefetch.start', 'prefetch.hit']:
            try:
                st = newd[rs] - oldd[rs]
                if tot == 0:
                    pct = 0
                else:
                    pct = float(st) / float(tot)
                print('\t\t%-24s %9d / %5.1f%%'
                      % (('%s:' % (rs,)), st, pct * 100.0))
            except:
                # This domain does not have this stat.
                pass
        print

    # L1
    st = new[MISSES_L1] - old[MISSES_L1]
    if pref_l1 == 0:
        pct = 0
    else:
        pct = float(st) / float(pref_l1)
    print 'l1_all:'
    print '\tprefetch.start:           %16d / %5.1f%%' % (pref_l1, 100.0)
    print '\tprefetch.misses:          %16d / %5.1f%%' % (st, pct * 100.0)

    # L2
    st = new[MISSES_L2] - old[MISSES_L2]
    if pref_l2 == 0:
        pct = 0
    else:
        pct = float(st) / float(pref_l2)
    print 'l2_all:'
    print '\tprefetch.start:           %16d / %5.1f%%' % (pref_l2, 100.0)
    print '\tprefetch.misses:          %16d / %5.1f%%' % (st, pct * 100.0)


def help():
    print "Usage: isi_cache_stats [-h|--help] [-v|--verbose] [-z] [interval]"

def main():
    delay = 0
    verbose = False
    clear = False

    try:
        opts, args = getopt.gnu_getopt(sys.argv[1:], "hvz", ["help", "verbose"])
    except getopt.GetoptError, err:
        print str(err)
        help()
        sys.exit(2)

    for o, a in opts:
        if o in ("-h", "--help"):
            help();
            sys.exit(0)
        if o in ("-v", "--verbose"):
            verbose = True
        if o in ("-z"):
            clear = True

    for a in args:
        try:
            delay = int(a)
        except:
            print "Didn't understand " + a
            help();
            sys.exit(2)

    new = gather()
    if verbose:
        print "------------------------- Totals -------------------------"
        display_verbose(new, empty)
    else:
        print "%-17s " % ('Totals'),
        display_simple(new, empty)

    if delay > 0:
        try:
            while True:
                old = new
                time.sleep(delay)
                new = gather()
                lt = time.localtime(time.time())
                if verbose:
                    print('------------------------- %s'
                          ' -------------------------' %
                          time.strftime('%c', lt))
                    display_verbose(new, old)
                else:
                    print("%-17s " % (time.strftime('%y/%m/%d %T', lt),))
                    display_simple(new, old)
        # Avoid ugly stack trace when user presses Ctrl-C.
        except KeyboardInterrupt:
            print()

    if clear:
        misc.sysctlbyname('isi.cache.stats.clear')

if __name__ == "__main__":
    main()
