#!/usr/bin/env python
# Copyright (c) 2018 Arista Networks, Inc.  All rights reserved.
# Arista Networks, Inc. Confidential and Proprietary.

#pylint: disable-msg=W0621,W0702,E1133,E1135,E1136

import os, json, optparse

parser = optparse.OptionParser( 
   usage="%prog [options] <file1> <file2>", 
   description="diff qtprof output" 
)

parser.add_option('-p', '--parseable',
                  dest='parsable', action='store_true', 
                  help=optparse.SUPPRESS_HELP)
parser.add_option('-l', '--long',
                  dest='long', action='store_true', 
                  help=optparse.SUPPRESS_HELP)
parser.add_option('-s', '--seconds',
                  dest='seconds', action='store_true', 
                  help='diff total times (seconds change; default option)')
parser.add_option('-t', '--total',
                  dest='metric', action='append_const', const='total',
                  help='diff total times (percent change)')
parser.add_option('-a', '--average',
                  dest='metric', action='append_const', const='average',
                  help='diff avg.  times (percent change)')
(options, args) = parser.parse_args()

if not options.metric and not options.seconds:
   metric = 'total'
   options.seconds = True
else:
   # we support only either -t or -a; not both
   metric = options.metric[0] if options.metric else 'total'
   if options.seconds:
      metric = 'total'

width = 100
try:
   _, width = os.popen('stty size', 'r').read().split()
   width = int(width)
except:
   pass

if len(args) != 2:
   parser.print_help()
   exit()

data = [0, 0]

# get the input data
for index, arg in enumerate(args):
   if options.parsable:
      with open(arg, 'r') as f: 
         value = f.read()
      data[index] = json.loads(f.read())
   elif arg.startswith('http://'):
      value = os.popen('curl -sSL %s | qtparse -b' % arg).read()
      if value:
         data[index] = json.loads(value) 
   elif arg.endswith('.qt'):
      value = os.popen('qtcat -j %s' % arg).read()
      if value:
         data[index] = json.loads(value)
   else:
      value = os.popen('qtparse -b %s' % arg).read()
      if value:
         data[index] = json.loads(value)

if not data[0] or not data[1]:
   print 'failed to process input'	
   exit()


KEY_QT = 0
KEY_FILE = 1
KEY_LINE = 2
KEY_MSG = 3 # i.e. function name

def reindex(qtprof):
   temp = {}
   for row in qtprof:
      key = ( 
         row['qt'],   # KEY_QT 
         row['file'], # KEY_FILE
         row['line'], # KEY_LINE
         row['msg']   # KEY_MSG
      )
      value = { k: row[k] for k in ['line', 'count', 'average', 'total'] } 
      if key not in temp:
         temp[key] = [value]
      else:
         temp[key] += [value]
   return temp

data[0] = reindex(data[0])
data[1] = reindex(data[1])

#
# compute the "diff" of two qtprof entries according to the metric that was chosen
# by the user. do some basic filtering here.
#
def diff( r1, r2 ):

   if not (r1['average'] and r2['average']): 
      return 0
   
   # do not consider if total time < 2 seconds
   if float(r1['total']) < 2: 
      return 0
  
   if options.seconds:
      change = float(r2[metric]) - float(r1[metric])
      # do not consider if change is within 2s of original
      if abs(change) < 2: 
         return 0
   else:
      change = float(r2[metric]) / float(r1[metric])
      # do not consider if change is within 5% of original
      if change < 1.05 and change > 0.95: 
         return 0
   
   return change

def display(key, change):
   CEND = '\33[0m'
   CRED = '\33[31m'
   CGRN = '\33[32m'
   if options.long:
      print "%-90s" % (key[KEY_MSG][:90]),
   else:
      print "%-50s" % (key[KEY_MSG][:50]),
   color = CRED if change > 1 else CGRN
   cend  = CEND
   sign  = '+'  if change > 1 else '-'
   if options.seconds:
      change = abs(int(change))
      label = 's'
   else:
      change = abs(int((change-1) * 100))
      label = '%'
   bars = sign * change if not options.long else ''
   print "%s%s%3d %s %s %s" % (color, sign, change, label, bars[:width-60], cend)

####################################################################################

changes = {}

#
# find the closest matching key in the given data
# match everything except for line-number because for qt collected over time, the
# line-numbers of the qt can change slightly over time
#
def closest(d, key):
   match = []
   for k in d:
      if all([k[i] == key[i] for i in [KEY_QT, KEY_FILE, KEY_MSG]]):
         match.append(k)
   return match[0] if len(match) == 1 else 0

#
# main calculation loop
#
for key0 in data[0]:

   val = [0, 0]

   if key0 not in data[1]:
      key1 = closest(data[1], key0)
   else:
      key1 = key0

   if not key1: 
      continue

   if len(data[0][key0]) > 1 or len(data[1][key1]) > 1:
      continue

   val[0] = data[0][key0][0]
   val[1] = data[1][key1][0]

   change = diff(val[0], val[1])

   if change:
      changes[key0] = change

#
# display the results
#
for key in sorted(changes, key=changes.get, reverse=True):
   display(key, changes[key])

if not changes:
   print "no significant changes in qt profiling snapshots"
