#!/usr/bin/env python3
import os
import sys
import subprocess
import glob
import csv
import datetime
import argparse

dbg_prt = False
cur_dir = os.path.dirname(__file__)
out_dir = None

def print_log(msg):
    BLUE = '\033[94m'
    ENDC = '\033[0m'
    print(BLUE + "# [report] " + msg + ENDC)

class stat_item:
    def __init__(self):
        # Information is stored in a list of tuples, each of which is a pair of
        # a property name (median FPS) and its value (60).
        self.stat = None

        # `*_diff` is a list of peformance difference against a baseline in %.
        # It is a list of float, and the order is the same of the original one.
        self.diff = None

    def calc_diff(self, baseline):
        # sanity check
        if self.stat == None or baseline.stat == None:
            return

        # self difference is always 0% difference.
        if self == baseline:
            self.diff = [0.0] * len(self.stat)
            return
        # calc delta over the basseline
        self.diff = []
        for (b, c) in zip(baseline.stat, self.stat):
            b_v = float(b[1])
            c_v = float(c[1])
            if b_v == c_v:
                d = 0.0
            else:
                if b_v == 0.0:
                    b_v = b_v + sys.float_info.min
                d = ((c_v - b_v) / b_v) * 100.0
            self.diff.append(d)
        global dbg_prt
        if dbg_prt:
            print(self.diff)

class stat_app:

    def __init__(self, ldir, prefix, force):
        self.ldir = os.path.abspath(ldir)
        self.prefix = prefix
        self.nick = self.ldir.split("-")[-1]
        self.force = force

        # scinsight
        # ---------
        # * system call statistics
        self.sc_top10 = []
        self.sc_thr_nr = 0

        # ginsight
        # --------
        # * FPS
        #   - 97p, median, 1p, 0.1p
        self.fps = stat_item()
        # * CPU util
        #   - mediam, max
        self.cpu_util =  stat_item()
        # * GPU util
        #   - mediam, max
        self.gpu_util = stat_item()
        # * RAM util
        #   - median, max
        self.ram_util =  stat_item()

        # factorio
        # --------
        # * processing time
        #   - avg, min, max
        self.factorio = stat_item()

        # schbench
        # --------
        # * wakeup_lat (usec)
        #   - min, 50.0th, 90.0th, 99.0th, 99.9th, max
        self.wakeup_lat = stat_item()
        # * req_lat (usec)
        #   - min, 50.0th, 90.0th, 99.0th, 99.9th, max
        self.req_lat = stat_item()
        # * throughput (request per second)
        #   - min, 20.0th, average, 50.0th, 90.0th, max
        self.rps= stat_item()

        # procinsight
        # -----------
        # * CPU power state
        #   - C0, poll, C1, c2, c3
        self.cpu_pwr = stat_item()
        # * Clock
        #   - freq
        self.clock_freq = stat_item()
        # * Energy
        #   - J, J/sec
        self.energy = stat_item()
        # * processor
        #   - ipc, front-end stall (%), back-end stall (%), page faults
        self.processor = stat_item()
        # * scheduling
        #   - context_switches, cpu-migrations, sched_wakeup
        self.sched = stat_item()

    def exec_insight(self, c, a):
        global cur_dir
        cmd = os.path.abspath(os.path.join(cur_dir, c)) + " " + a
        print_log("Running %s" % cmd)
        p = subprocess.Popen(cmd, shell=True, stdout=None, stderr=None)
        p.wait()
        return p

    def all_csv_exist(self, insight, props):
        for p in props:
            l = os.path.join(self.ldir, self.prefix + "-" + insight + "-" + p + ".csv")
            if os.path.isfile(l) == False:
                return False
        return True

    def build_sc_stat(self):
        # count the number of threads
        scm_logs = glob.glob(os.path.join(self.ldir, self.prefix + "-scmon.*"))
        self.sc_thr_nr = len(scm_logs)
        if self.sc_thr_nr == 0:
            return

        # if sc_log does not exists, generate it.
        props = ["stat"]
        if self.force == True or self.all_csv_exist("scinsight", props) == False:
            arg = "-q -o {outdir} -l {log}".format(
                    outdir = self.ldir, log = self.prefix)
            self.exec_insight("scinsight", arg)
            if self.all_csv_exist("scinsight", props) == False:
                return

        # load the stat CSV
        sc_log = os.path.join(self.ldir, self.prefix + "-scinsight-stat.csv")
        with open(sc_log, "r") as f:
            rd = csv.reader(f)
            rd.__next__()
            for i in range(0,10):
                s, _, r = rd.__next__()
                self.sc_top10.append( (s.strip(), r.strip()) )

    def build_data_list(self, insight, prop, keys):
        l = os.path.join(self.ldir, self.prefix + 
                         "-" + insight + "-" + prop  + ".csv")
        with open(l, "r") as f:
            rd = csv.reader(f)
            rd.__next__()
            data= {}
            for s, fps in rd:
                data[s.strip()] = fps.strip()
            dlist = []
            for k in keys:
                dlist.append( (k, data[k]) )
            global dbg_prt
            if dbg_prt:
                print(dlist)
            return dlist

    def build_g_stat(self):
        # if there is a missing CSV, generate all CSVs
        props = ["cpu_load", "gpu_load", "fps", "ram_used"]
        if self.force == True or self.all_csv_exist("ginsight", props) == False:
            mlog = os.path.join(self.ldir, self.prefix + "-mangohud.csv")
            arg = "-q -l {mlog} -o {outdir} -p {prefix}".format(
                    mlog = mlog, outdir = self.ldir, prefix = self.prefix)
            self.exec_insight("ginsight", arg)
            if self.all_csv_exist("ginsight", props) == False:
                return

        # * FPS
        #   - 97p, median, 1p, 0.1p
        keys = ["Low 97%", "Median", "Low 1%", "Low 0.1%"]
        self.fps.stat = self.build_data_list("ginsight", "fps", keys)

        # * CPU util, GPU util, RAM util
        #   - mediam, max
        keys = ["Median", "Max"]
        self.cpu_util.stat = self.build_data_list("ginsight", "cpu_load", keys)
        self.gpu_util.stat = self.build_data_list("ginsight", "gpu_load", keys)
        self.ram_util.stat = self.build_data_list("ginsight", "ram_used", keys)

    def build_factorio_stat(self):
        flog = os.path.join(self.ldir, self.prefix + ".factorio_out")
        if os.path.isfile(flog) == False:
            return

        with open(flog, "r") as f:
            print_log("Loading %s" % flog)
            for line in f:
                line = line.strip()
                if line.startswith("avg:") == False:
                    continue
                # avg: 76.110 ms, min: 70.599 ms, max: 233.318 ms
                dlist = []
                for tok in line.split(","):
                    k, v = tok.split(":")
                    v = v[:-3].strip()
                    dlist.append( (k, v) )
                self.factorio.stat = dlist
                break
            global dbg_prt
            if dbg_prt:
                print(self.factoriot.stat)

    def build_schbench_stat(self):
        flog = os.path.join(self.ldir, self.prefix + ".schbench_out")
        if os.path.isfile(flog) == False:
            return

        class STAT:
            # state
            UNKNOWN = 0
            WAKEUP_LAT = 1
            REQ_LAT = 2
            RPS = 3

            # state transition map
            trans = [("Wakeup Latencies percentiles", WAKEUP_LAT),
                     ("Request Latencies percentiles", REQ_LAT),
                     ("RPS percentiles", RPS)]

            def __init__(self, sa):
                self.stat_app = sa
                self.s = STAT.UNKNOWN
                self.new_s = STAT.UNKNOWN
                self.dlist = []

            def is_new_state(self, line):
                for prefix, new_s in self.trans:
                    if line.startswith(prefix):
                        self.new_s = new_s
                        return True
                return False

            def trans_state(self):
                # wrarp up the current state
                if self.s == self.WAKEUP_LAT:
                    self.stat_app.wakeup_lat.stat = self.dlist
                elif self.s == self.REQ_LAT:
                    self.stat_app.req_lat.stat = self.dlist
                elif self.s == self.RPS:
                    self.stat_app.rps.stat = self.dlist
                # prep for the new state
                self.dlist = []
                self.s = self.new_s
                self.new_s = self.UNKNOWN

        s = STAT(self)
        with open(flog, "r") as f:
            for line in f:
                # transitioning to a new state
                if s.is_new_state(line):
                    s.trans_state()
                    continue
                # continue on the old state
                # * clean up lines
                line = line.strip()
                if line.startswith("current rps:") or \
                        line.startswith("final rps goal was") or \
                        line.startswith("setting worker threads") or \
                        line.startswith("#") or line == "": \
                    continue
                if line.startswith("* "):
                    line = line[2:].strip()
                if line.find("(") != -1:
                    line = line[: line.find("(")]
                line = line.strip()
                # * parsing
                #   - min=7403, max=7900
                if line.startswith("min"):
                    mx_list = line.split(",")
                    for mx in mx_list:
                        kv = mx.split("=")
                        s.dlist.append( (kv[0].strip(), kv[1].strip()) )
                #   - 20.0th: 7544
                #   - average rps: 7582.38
                else:
                    kv = line.split(":")
                    s.dlist.append( (kv[0].strip(), kv[1].strip()) )

    def build_proc_stat(self):
        # if there is a missing CSV, generate all CSVs
        props = ["cstate-sw", "energy-sw", "perf-sw", "sched-sw"]
        if self.force == True or self.all_csv_exist("procinsight", props) == False:
            arg = "-q -o {outdir} -l {prefix}".format(
                    outdir = self.ldir, prefix = self.prefix)
            self.exec_insight("procinsight", arg)
            if self.all_csv_exist("procinsight", props) == False:
                return

        # * CPU power state
        #   - C0, poll, C1, c2, c3
        keys = ["C0", "POLL", "C1", "C2", "C3"]
        self.cpu_pwr.stat = self.build_data_list("procinsight", "cstate-sw", keys)
        # * Clock
        #   - freq
        keys = ["Freq"]
        self.clock_freq.stat = self.build_data_list("procinsight", "cstate-sw", keys)
        # * Energy
        #   - J, J/sec
        keys= ["J", "J/sec"]
        self.energy.stat = self.build_data_list("procinsight", "energy-sw", keys)
        # * processor
        #   - ipc, front-end stall (%), back-end stall (%), page faults
        keys = ["ipc", "frontend-stall (%)", "backend-stall (%)", "page-faults"]
        self.processor.stat = self.build_data_list("procinsight", "perf-sw", keys)
        # * scheduling
        #   - context_switches, cpu-migrations, sched_wakeup
        keys = ["context-switches", "cpu-migrations"]
        self.sched.stat = self.build_data_list("procinsight", "perf-sw", keys)
        keys = ["sched_wakeup"]
        self.sched.stat = self.sched.stat + \
                self.build_data_list("procinsight", "sched-sw", keys)

    def build_stat(self):
        self.build_sc_stat()
        self.build_factorio_stat()
        self.build_schbench_stat()
        self.build_g_stat()
        self.build_proc_stat()

    def calc_diff(self, baseline):
        self.factorio.calc_diff(baseline.factorio)
        self.wakeup_lat.calc_diff(baseline.wakeup_lat)
        self.req_lat.calc_diff(baseline.req_lat)
        self.rps.calc_diff(baseline.rps)
        self.fps.calc_diff(baseline.fps)
        self.cpu_util.calc_diff(baseline.cpu_util)
        self.gpu_util.calc_diff(baseline.gpu_util)
        self.ram_util.calc_diff(baseline.ram_util)
        self.cpu_pwr.calc_diff(baseline.cpu_pwr)
        self.clock_freq.calc_diff(baseline.clock_freq)
        self.energy.calc_diff(baseline.energy)
        self.processor.calc_diff(baseline.processor)
        self.sched.calc_diff(baseline.sched)

def build_app_stats(args):
    # buid app stat for each log directory
    app_stats = []
    for logdir in args.logdir:
        stat = stat_app(logdir, args.prefix, args.force)
        stat.build_stat()
        app_stats.append(stat)

    # calculate diff in %
    baseline = app_stats[0]
    for comp in app_stats:
        comp.calc_diff(baseline)

    return app_stats

def get_res_path(ldir, fname):
    global out_dir
    afil = os.path.join(ldir, fname)
    rfil = os.path.relpath(afil, start = out_dir)
    return afil, rfil

def gen_md_tbl(stat_app, suffix, tuples, header, f):
    nick = stat_app.nick
    afil, rfil = get_res_path(stat_app.ldir, stat_app.prefix + suffix)

    # generate header row
    if header:
        l1, l2 = "|    |", "| ---- |"
        for t in tuples:
            l1 = l1 + " " + t[0] + " | "
            l2 = l2 + " ---: | "
        print(l1, file = f)
        print(l2, file = f)
    # generate data
    if os.path.isfile(afil):
        l = "| [**" + nick + "**](" + rfil + ") |"
    else:
        l = "| **" + nick + "** |"
    for t in tuples:
        l = l + " " + t[1] + " | "
    print(l, file = f)

def get_style(d):
    if d == 0:
        return ""
    if d > 0.0:
        return "**"
    return "*"

def gen_md_diff_tbl(stat_app, suffix, st_item, baseline, f):
    nick = stat_app.nick
    afil, rfil = get_res_path(stat_app.ldir, stat_app.prefix + suffix)

    # generate header row for baseline
    if baseline:
        l1, l2 = "|    |", "| ---- |"
        for s in st_item.stat:
            l1 = l1 + " " + s[0] + " | "
            l2 = l2 + " ---: | "
        print(l1, file = f)
        print(l2, file = f)
    # generate data
    if os.path.isfile(afil):
        l = "| [**" + nick + "**](" + rfil + ") |"
    else:
        l = "| **" + nick + "** |"
    for (s, d) in zip(st_item.stat, st_item.diff):
        style = get_style(d)
        l = l + " " + style + s[1] 
        if baseline == False:
            l = l + " (%.4f" % d + "%)"
        l = l + style + " | "
    print(l, file = f)

def gen_report_config(args, app_stats, f):
    # title = prefix
    print("``` {=html}", file = f)
    print("<style>", file = f)
    print("body { min-width: 80% !important; }", file = f)
    print("</style>", file = f)
    print("```", file = f)

    print("---", file = f)
    print("title: %s" % args.prefix, file = f)
    print("date: %s" % datetime.datetime.now(), file = f)
    print("---", file = f)
    print("\n\n", file = f)

    # #### Comparisons
    print("### Comparisons\n", file = f)
    for s in app_stats:
        print("- **%s**: %s" % (s.nick, s.ldir), file = f)
    print("\n\n", file = f)

def gen_report_syscall(args, app_stats, f):
    for s in app_stats:
        if s.sc_thr_nr == 0:
            continue
        print("### System call\n", file = f)
        print("- Number of threads involved: %d\n" % s.sc_thr_nr, file = f)
        print("- Top 10 system calls (%)\n", file = f)
        gen_md_tbl(s, "-scinsight-stat.svg", s.sc_top10, True, f)
    print("\n\n", file = f)

def gen_report_factorio(args, app_stats, f):
    s = app_stats[0]
    if s.factorio.stat == None:
        return
    print("### Map update time in msec\n", file = f)
    gen_md_diff_tbl(s, ".factorio_out", s.factorio, True, f)
    for s in app_stats[1:]:
        if s.factorio== None:
            continue
        gen_md_diff_tbl(s, ".factorio_out", s.factorio, False, f)
    print("\n\n", file = f)

def gen_report_schbench_rps(args, app_stats, f):
    s = app_stats[0]
    if s.rps.stat == None:
        return
    print("### Request per second (RPS)\n", file = f)
    gen_md_diff_tbl(s, ".schbench_out", s.rps, True, f)
    for s in app_stats[1:]:
        if s.rps == None:
            continue
        gen_md_diff_tbl(s, ".schbench_out", s.rps, False, f)
    print("\n\n", file = f)

def gen_report_schbench_req_lat(args, app_stats, f):
    s = app_stats[0]
    if s.req_lat.stat == None:
        return
    print("### Request latencies (usec)\n", file = f)
    gen_md_diff_tbl(s, ".schbench_out", s.req_lat, True, f)
    for s in app_stats[1:]:
        if s.req_lat == None:
            continue
        gen_md_diff_tbl(s, ".schbench_out", s.req_lat, False, f)
    print("\n\n", file = f)

def gen_report_schbench_wakeup_lat(args, app_stats, f):
    s = app_stats[0]
    if s.wakeup_lat.stat == None:
        return
    print("### Wakeup latencies (usec)\n", file = f)
    gen_md_diff_tbl(s, ".schbench_out", s.wakeup_lat, True, f)
    for s in app_stats[1:]:
        if s.wakeup_lat == None:
            continue
        gen_md_diff_tbl(s, ".schbench_out", s.wakeup_lat, False, f)
    print("\n\n", file = f)

def gen_report_schbench(args, app_stats, f):
    gen_report_schbench_rps(args, app_stats, f)
    gen_report_schbench_req_lat(args, app_stats, f)
    gen_report_schbench_wakeup_lat(args, app_stats, f)

def gen_report_fps(args, app_stats, f):
    s = app_stats[0]
    if s.fps.stat == None:
        return
    print("### FPS\n", file = f)
    gen_md_diff_tbl(s, "-ginsight-fps.svg", s.fps, True, f)
    for s in app_stats[1:]:
        if s.fps == None:
            continue
        gen_md_diff_tbl(s, "-ginsight-fps.svg", s.fps, False, f)
    print("\n\n", file = f)


def gen_report_cpu_util(args, app_stats, f):
    s = app_stats[0]
    if s.cpu_util.stat == None:
        return
    print("### CPU utilization\n", file = f)
    gen_md_diff_tbl(s, "-ginsight-cpu_load.svg", s.cpu_util, True, f)
    for s in app_stats[1:]:
        if s.cpu_util == None:
            continue
        gen_md_diff_tbl(s, "-ginsight-cpu_load.svg", s.cpu_util, False, f)
    print("\n\n", file = f)

def gen_report_gpu_util(args, app_stats, f):
    s = app_stats[0]
    if s.gpu_util.stat == None:
        return
    print("### GPU utilization\n", file = f)
    gen_md_diff_tbl(s, "-ginsight-gpu_load.svg", s.gpu_util, True, f)
    for s in app_stats[1:]:
        if s.gpu_util == None:
            continue
        gen_md_diff_tbl(s, "-ginsight-gpu_load.svg", s.gpu_util, False, f)
    print("\n\n", file = f)

def gen_report_ginsight_overview(args, app_stats, f):
    # print header
    #  |      | conf1 | conf2 | conf3 |
    #  | ---- | ----- | ----- | ----- |
    print("### Performance overview\n", file = f)
    l1, l2 = "|      |", "| :----: |"
    for s in app_stats:
        l1 = l1 + " " + s.nick + " | "
        l2 = l2 + " :---: | "
    print(l1, file = f)
    print(l2, file = f)

    # print graphs
    #  | FPS  | img1  | img2  | img3  |
    #  | cpu  | img1  | img2  | img3  |
    #  | gpu  | img1  | img2  | img3  |
    #  | ram  | img1  | img2  | img3  |
    class report_conf:
        name = ""
        suffix = ""
        def __init__(self, n, s):
            self.name = n
            self.suffix = s

    confs = [report_conf("FPS", "-ginsight-fps.svg"), 
             report_conf("CPU", "-ginsight-cpu_load.svg"),
             report_conf("GPU", "-ginsight-gpu_load.svg"),
             report_conf("RAM", "-ginsight-ram_used.svg"),]
    for c in confs:
        l1 = "| " + c.name + " |"
        for s in app_stats:
            afil, rfil = get_res_path(s.ldir, s.prefix + c.suffix)
            img = "![](" + rfil + ")"
            l1 = l1 + " " + img + " | "
        print(l1, file = f)
    print("\n\n", file = f)

def gen_report_ram_util(args, app_stats, f):
    s = app_stats[0]
    if s.ram_util.stat == None:
        return
    print("### RAM usage\n", file = f)
    gen_md_diff_tbl(s, "-ginsight-ram_used.svg", s.ram_util, True, f)
    for s in app_stats[1:]:
        if s.ram_util == None:
            continue
        gen_md_diff_tbl(s, "-ginsight-ram_used.svg", s.ram_util, False, f)
    print("\n\n", file = f)

def gen_report_cpu_pwr(args, app_stats, f):
    s = app_stats[0]
    if s.cpu_pwr.stat == None:
        return
    print("### CPU power state\n", file = f)
    gen_md_diff_tbl(s, "-procinsight-cstate-core.csv", s.cpu_pwr, True, f)
    for s in app_stats[1:]:
        if s.cpu_pwr == None:
            continue
        gen_md_diff_tbl(s, "-procinsight-cstate-core.csv", s.cpu_pwr, False, f)
    print("\n\n", file = f)

def gen_report_clock_freq(args, app_stats, f):
    s = app_stats[0]
    if s.clock_freq.stat == None:
        return
    print("### Clock frequency\n", file = f)
    gen_md_diff_tbl(s, "-procinsight-cstate-core.csv", s.clock_freq, True, f)
    for s in app_stats[1:]:
        if s.clock_freq == None:
            continue
        gen_md_diff_tbl(s, "-procinsight-cstate-core.csv", s.clock_freq, False, f)
    print("\n\n", file = f)

def gen_report_energy(args, app_stats, f):
    s = app_stats[0]
    if s.energy.stat == None:
        return
    print("### Energy consumption \n", file = f)
    gen_md_diff_tbl(s, "-procmon-energy__.log", s.energy, True, f)
    for s in app_stats[1:]:
        if s.energy == None:
            continue
        gen_md_diff_tbl(s, "-procmon-energy__.log", s.energy, False, f)
    print("\n\n", file = f)

def gen_report_processor(args, app_stats, f):
    s = app_stats[0]
    if s.processor.stat == None:
        return
    print("### Processor state\n", file = f)
    gen_md_diff_tbl(s, "-procinsight-perf-sw.csv", s.processor, True, f)
    for s in app_stats[1:]:
        if s.processor == None:
            continue
        gen_md_diff_tbl(s, "-procinsight-perf-sw.csv", s.processor, False, f)
    print("\n\n", file = f)

def gen_report_sched(args, app_stats, f):
    s = app_stats[0]
    if s.sched.stat == None:
        return
    print("### Scheduling state\n", file = f)
    gen_md_diff_tbl(s, "-procinsight-sched-core.csv", s.sched, True, f)
    for s in app_stats[1:]:
        if s.sched == None:
            continue
        gen_md_diff_tbl(s, "-procinsight-sched-core.csv", s.sched, False, f)
    print("\n\n", file = f)

def gen_report(args, app_stats):
    # generate a report in markdown format
    file_md = args.output + ".md"
    with open(file_md, "w") as f:
        gen_report_config(args, app_stats, f)
        gen_report_syscall(args, app_stats, f)
        gen_report_factorio(args, app_stats, f)
        gen_report_schbench(args, app_stats, f)
        gen_report_ginsight_overview(args, app_stats, f)
        gen_report_fps(args, app_stats, f)
        gen_report_sched(args, app_stats, f)
        gen_report_cpu_util(args, app_stats, f)
        gen_report_gpu_util(args, app_stats, f)
        gen_report_ram_util(args, app_stats, f)
        gen_report_cpu_pwr(args, app_stats, f)
        gen_report_clock_freq(args, app_stats, f)
        gen_report_energy(args, app_stats, f)
        gen_report_processor(args, app_stats, f)

    # convert the markdown report to html
    file_html = args.output + ".html"
    global cur_dir
    cmd = "pandoc --standalone --toc %s -o %s" % (file_md, file_html)
    print_log("Running %s" % cmd)
    p = subprocess.Popen(cmd, shell=True, stdout=None, stderr=None)
    p.wait()

def get_cmd_options(argv):
    parser = argparse.ArgumentParser(
            prog = "report",
            description = "Generate a report of given log directories",
            epilog = """
For example, `report -l base_dir -l cmp_dir -p game1 -o report.md` compares `game1` logs in two directoreis -- `base_dir` and `cmp_dir` -- and generates `report.md`. `base_dir` is used in calculating the relative difference. When only one log directory is given, only the summary of results without comparison is provided. It expects certain file extensions: `*.factorio_out` for factorio benchmark and `*.schbench_out` for schbench benchmark.
            """)

    parser.add_argument('-l', '--logdir', action='append', required=True,
                        help='a log directory. When mulltiple `-l` options ' \
                             'are given, comparison will be reported using ' \
                             'the first one as a baseline.') 
    parser.add_argument('-p', '--prefix', action='store', required=True,
                        help='log file prefix for report generation') 
    parser.add_argument('-o', '--output', action='store', required=True,
                        help='target report file name in markdown format') 
    parser.add_argument('-f', '--force', action='store_true',
                        help='force to regenerate all CSV files') 
    parser.add_argument('-g', '--debug', action='store_true',
                        help='print out debug messages') 
    args = parser.parse_args(argv)
    global dbg_prt
    dbg_prt = args.debug
    global out_dir
    out_dir = os.path.dirname(args.output)
    return args

if __name__ == "__main__":
    args = get_cmd_options(sys.argv[1:])
    app_stats = build_app_stats(args)
    gen_report(args, app_stats)

