import sys
import os
from settings import tikv_ip, tikv_port, tikv_pd_ip, ycsb_port, ansibledir, deploydir
import psutil
import time
import numpy as np
from ruamel import yaml

#MEM_MAX = psutil.virtual_memory().total
MEM_MAX = 0.8*32*1024*1024*1024                 # memory size of tikv node, not current PC


#------------------knob controller------------------

# disable_auto_compactions
def set_disable_auto_compactions(ip, port, val):
    cmd="./tikv-ctl --host "+ip+":"+port+" modify-tikv-config -m kvdb -n default.disable_auto_compactions -v "+str(val)
    res=os.popen(cmd).read()                        # will return "success"
    return(res)

knob_set=\
    {
    "rocksdb.defaultcf.write-buffer-size":
        {
            "changebyyml": True,
            "set_func": None,
            "minval": 64,                           # if type==int, indicate min possible value
            "maxval": 1024,                         # if type==int, indicate max possible value
            "enumval": [],                          # if type==enum, list all valid values
            "type": "int",                          # int / enum
            "default": 64                           # default value
        },
    "rocksdb.defaultcf.max-bytes-for-level-base":
        {
            "changebyyml": True,
            "set_func": None,
            "minval": 512,                          # if type==int, indicate min possible value
            "maxval": 4096,                         # if type==int, indicate max possible value
            "enumval": [],                          # if type==enum, list all valid values
            "type": "int",                          # int / enum
            "default": 512                            # default value
        },
    "rocksdb.defaultcf.target-file-size-base":
        {
            "changebyyml": True,
            "set_func": None,
            "minval": 0,                            # if type==int, indicate min possible value
            "maxval": 0,                            # if type==int, indicate max possible value
            "enumval": [8,16,32,64,128],            # if type==enum, list all valid values
            "type": "enum",                         # int / enum
            "default": 8                            # default value
        },
    "rocksdb.defaultcf.disable-auto-compactions":
        {
            "changebyyml": True,
            "set_func": None,
            "minval": 0,                            # if type==int, indicate min possible value
            "maxval": 0,                            # if type==int, indicate max possible value
            "enumval": ['false', 'true'],           # if type==enum, list all valid values
            "type": "bool",                         # int / enum
            "default": 0                            # default value
        },
    "rocksdb.defaultcf.block-size":
        {
            "changebyyml": True,
            "set_func": None,
            "minval": 0,                            # if type==int, indicate min possible value
            "maxval": 0,                            # if type==int, indicate max possible value
            "enumval": [4, 8, 16, 32, 64],          # if type==enum, list all valid values
            "type": "enum",                         # int / enum
            "default": 0                            # default value
        },
    "rocksdb.defaultcf.bloom-filter-bits-per-key":
        {
            "changebyyml": True,
            "set_func": None,
            "minval": 0,                            # if type==int, indicate min possible value
            "maxval": 0,                            # if type==int, indicate max possible value
            "enumval": [5,10,15,20],                # if type==enum, list all valid values
            "type": "enum",                         # int / enum
            "default": 0                            # default value
        },
    "rocksdb.writecf.bloom-filter-bits-per-key":
        {
            "changebyyml": True,
            "set_func": None,
            "minval": 0,                            # if type==int, indicate min possible value
            "maxval": 0,                            # if type==int, indicate max possible value
            "enumval": [5,10,15,20],                # if type==enum, list all valid values
            "type": "enum",                         # int / enum
            "default": 0                            # default value
        },
    "rocksdb.writecf.optimize-filters-for-hits":
        {
            "changebyyml": True,
            "set_func": None,
            "minval": 0,                            # if type==int, indicate min possible value
            "maxval": 0,                            # if type==int, indicate max possible value
            "enumval": ['false', 'true'],           # if type==enum, list all valid values
            "type": "bool",                         # int / enum
            "default": 0                            # default value
        },
    }


#------------------metric controller------------------

def read_write_throughput(ip, port):
    return(0)           # DEPRECATED FUNCTION: throughput is instant and could be read from go-ycsb. No need to read in this function

def read_write_latency(ip, port):
    return(0)           # DEPRECATED FUNCTION: latency is instant and could be read from go-ycsb. No need to read in this function

def read_get_throughput(ip, port):
    return(0)           # DEPRECATED FUNCTION: throughput is instant and could be read from go-ycsb. No need to read in this function

def read_get_latency(ip, port):
    return(0)           # DEPRECATED FUNCTION: latency is instant and could be read from go-ycsb. No need to read in this function

def read_scan_throughput(ip, port):
    return(0)           # DEPRECATED FUNCTION: throughput is instant and could be read from go-ycsb. No need to read in this function

def read_scan_latency(ip, port):
    return(0)           # DEPRECATED FUNCTION: latency is instant and could be read from go-ycsb. No need to read in this function

def read_store_size(ip, port):
    cmd='./tikv-ctl --host '+ip+':'+port+' metrics'
    res=os.popen(cmd).read()
    reslist=res.split("\n")
    ans0 =0
    for rl in reslist:
        if ('tikv_engine_size_bytes{db="kv",type="default"}' in rl):
            ans0 = int(rl.split(' ')[1])
            break
    return(ans0)

def read_compaction_cpu(ip, port):
    cmd='./tikv-ctl --host '+ip+':'+port+' metrics'
    res=os.popen(cmd).read()
    reslist=res.split("\n")
    ans=0
    ans1=0
    for rl in reslist:
        if ('tikv_thread_cpu_seconds_total{name="rocksdb:low' in rl):
            ans1 = float(rl.split(' ')[1])
            ans+=ans1
    return(ans)

metric_set=\
    {"write_throughput":
         {
         "read_func": read_write_throughput,
         "lessisbetter": 0,                   # whether less value of this metric is better(1: yes)
         "calc": "ins",                       #incremental
         },
    "write_latency":
        {
         "read_func": read_write_latency,
         "lessisbetter": 1,                    # whether less value of this metric is better(1: yes)
         "calc": "ins",                       #instant
        },
    "get_throughput":
        {
         "read_func": read_get_throughput,
         "lessisbetter": 0,                   # whether less value of this metric is better(1: yes)
         "calc": "ins",                       #incremental
        },
    "get_latency":
        {
         "read_func": read_get_latency,
         "lessisbetter": 1,                   # whether less value of this metric is better(1: yes)
         "calc": "ins",                       #instant
        },
    "scan_throughput":
        {
         "read_func": read_scan_throughput,
         "lessisbetter": 0,                   # whether less value of this metric is better(1: yes)
         "calc": "ins",                       #incremental
        },
    "scan_latency":
        {
         "read_func": read_scan_latency,
         "lessisbetter": 1,                   # whether less value of this metric is better(1: yes)
         "calc": "ins",                       #instant
        },
    "store_size":
        {
         "read_func": read_store_size,
         "lessisbetter": 1,                   # whether less value of this metric is better(1: yes)
         "calc": "ins",                       #instant
        },
    "compaction_cpu":
        {
         "read_func": read_compaction_cpu,
         "lessisbetter": 1,                   # whether less value of this metric is better(1: yes)
         "calc": "inc",                       #incremental
        },
    }


#------------------workload controller------------------

def run_workload(wl_type):
    #./go-ycsb run tikv -P ./workloads/smallpntlookup -p tikv.pd=192.168.1.130:2379
    cmd="./go-ycsb run tikv -P ./workloads/"+wl_type+" -p tikv.pd="+tikv_pd_ip+':'+ycsb_port+" --threads=512"
    print(cmd)
    res=os.popen(cmd).read()
    return(res)

def load_workload(wl_type):
    #./go-ycsb load tikv -P ./workloads/smallpntlookup -p tikv.pd=192.168.1.130:2379
    # cmd="./tikv-ctl --host "+tikv_ip+":"+tikv_port+" modify-tikv-config -m kvdb -n default.disable_auto_compactions -v 1"
    # tmp=os.popen(cmd).read()                        # will return "success"
    cmd="./go-ycsb load tikv -P ./workloads/"+wl_type+" -p tikv.pd="+tikv_pd_ip+':'+ycsb_port+" --threads=512"
    print(cmd)
    res=os.popen(cmd).read()
    # cmd="./tikv-ctl --host "+tikv_ip+":"+tikv_port+" compact -d kv --threads=512"
    # tmp=os.popen(cmd).read()                        # will return "success"
    return(res)


#------------------common functions------------------

def set_tikvyml(knob_sessname, knob_val):
    knob_sess=knob_sessname.split('.')[0:-1]
    knob_name=knob_sessname.split('.')[-1]
    ymldir=os.path.join(ansibledir,"conf","tikv_newcfg.yml")
    tmpdir=os.path.join(ansibledir,"conf","tikv.yml")
    tmpf=open(tmpdir)
    tmpcontent=yaml.load(tmpf, Loader=yaml.RoundTripLoader)
    if(knob_set[knob_sessname]['type']=='enum'):
        idx=knob_val
        knob_val=knob_set[knob_sessname]['enumval'][idx]
    if(knob_set[knob_sessname]['type']=='bool'):
        if(knob_val==0):
            knob_val=False
        else:
            knob_val=True
    if(knob_name=='block-size'):
        knob_val=str(knob_val)+"KB"
    if(knob_name=='write-buffer-size' or knob_name=='max-bytes-for-level-base' or knob_name=='target-file-size-base'):
        knob_val=str(knob_val)+"MB"
    if(knob_name in tmpcontent[knob_sess[0]][knob_sess[1]]):        # TODO: only support 2 level of knob_sess currently
        tmpcontent[knob_sess[0]][knob_sess[1]][knob_name]=knob_val
    else:
        return('failed')
    print("set_tikvyml:: ",knob_sessname, knob_sess, knob_name, knob_val)
    ymlf=open(ymldir, 'w')
    yaml.dump(tmpcontent, ymlf, Dumper=yaml.RoundTripDumper)
    os.popen("rm "+tmpdir+" && "+"mv "+ymldir+" "+tmpdir)
    time.sleep(0.5)
    return('success')

def set_knob(knob_name, knob_val):
    changebyyml=knob_set[knob_name]["changebyyml"]
    if(changebyyml):
        res=set_tikvyml(knob_name, knob_val)
    else:
        func=knob_set[knob_name]["set_func"]
        res=func(tikv_ip, tikv_port, knob_val)
    return res

def read_knob(knob_name, knob_cache):
    res=knob_cache[knob_name]
    return res

def read_metric(metric_name, rres=None):
    if(rres!=None):
        rl=rres.split('\n')
        rl.reverse()
        if(metric_name=="write_latency"):
            i=0
            while((not rl[i].startswith('UPDATE ')) and (not rl[i].startswith('INSERT '))):
                i+=1
            dat=rl[i][rl[i].find("Avg(us):") + 9:].split(",")[0]
            dat=int(dat)
            return(dat)
        elif(metric_name=="get_latency"):
            i=0
            while(not rl[i].startswith('READ ')):
                i+=1
            dat=rl[i][rl[i].find("Avg(us):") + 9:].split(",")[0]
            dat=int(dat)
            return(dat)
        elif(metric_name=="scan_latency"):
            i=0
            while(not rl[i].startswith('SCAN ')):
                i+=1
            dat=rl[i][rl[i].find("Avg(us):") + 9:].split(",")[0]
            dat=int(dat)
            return(dat)
        elif(metric_name=="write_throughput"):
            i=0
            while((not rl[i].startswith('UPDATE ')) and (not rl[i].startswith('INSERT '))):
                i+=1
            dat=rl[i][rl[i].find("OPS:") + 5:].split(",")[0]
            dat=float(dat)
            return(dat)
        elif(metric_name=="get_throughput"):
            i=0
            while(not rl[i].startswith('READ ')):
                i+=1
            dat=rl[i][rl[i].find("OPS:") + 5:].split(",")[0]
            dat=float(dat)
            return(dat)
        elif(metric_name=="scan_throughput"):
            i=0
            while(not rl[i].startswith('SCAN ')):
                i+=1
            dat=rl[i][rl[i].find("OPS:") + 5:].split(",")[0]
            dat=float(dat)
            return(dat)
    func=metric_set[metric_name]["read_func"]
    res=func(tikv_ip, tikv_port)
    return res

def init_knobs():
    # if there are knobs whose range is related to PC memory size, initialize them here
    pass

def calc_metric(metric_after, metric_before, metric_list):
    num_metrics = len(metric_list)
    new_metric = np.zeros([1, num_metrics])
    for i, x in enumerate(metric_list):
        if(metric_set[x]["calc"]=="inc"):
            new_metric[0][i]=metric_after[0][i]-metric_before[0][i]
        elif(metric_set[x]["calc"]=="ins"):
            new_metric[0][i]=metric_after[0][i]
    return(new_metric)

def restart_db():
    #cmd="cd /home/tidb/tidb-ansible/ && ansible-playbook unsafe_cleanup_data.yml"
    dircmd="cd "+ ansibledir + " && "
    clrcmd="ansible-playbook unsafe_cleanup_data.yml"
    depcmd="ansible-playbook deploy.yml"
    runcmd="ansible-playbook start.yml"
    ntpcmd="ansible-playbook -i hosts.ini deploy_ntp.yml -u tidb -b"   #need sleep 10s after ntpcmd
    print("-------------------------------------------------------")
    clrres = os.popen(dircmd+clrcmd).read()
    if("Congrats! All goes well" in clrres):
        print("unsafe_cleanup_data finished, res == "+clrres.split('\n')[-2])
    else:
        print(clrres)
        print("unsafe_cleanup_data failed")
        exit()
    print("-------------------------------------------------------")
    ntpres = os.popen(dircmd + ntpcmd).read()
    time.sleep(10)
    if ("Congrats! All goes well" in ntpres):
        print("set ntp finished, res == " + ntpres.split('\n')[-2])
    else:
        print(ntpres)
        print("set ntp failed")
        exit()
    print("-------------------------------------------------------")
    depres = os.popen(dircmd + depcmd).read()
    if ("Congrats! All goes well" in depres):
        print("deploy finished, res == "+depres.split('\n')[-2])
    else:
        print(depres)
        print("deploy failed")
        exit()
    print("-------------------------------------------------------")
    runres = os.popen(dircmd + runcmd).read()
    if ("Congrats! All goes well" in runres):
        print("start finished, res == "+runres.split('\n')[-2])
    else:
        print(runres)
        print("start failed")
        exit()
    print("-------------------------------------------------------")