#!/bin/env python

##
# Copyright(c) 2010-2015 Intel Corporation.
# Copyright(c) 2016-2018 Viosoft Corporation.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
#   * Redistributions of source code must retain the above copyright
#     notice, this list of conditions and the following disclaimer.
#   * Redistributions in binary form must reproduce the above copyright
#     notice, this list of conditions and the following disclaimer in
#     the documentation and/or other materials provided with the
#     distribution.
#   * Neither the name of Intel Corporation nor the names of its
#     contributors may be used to endorse or promote products derived
#     from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
##

from prox import *
from remotesystem import *
from time import *
from decimal import *
from timeseriespoint import *

class TestParameters:
    def __init__(self, max_setup_rate, total_connections, ss):
        self.max_setup_rate = max_setup_rate;
        self.total_connections = total_connections;
        self.ss = ss;

    def toString(self):
        ret = ""
        ret += "\tMaximum setup rate          = %d\n" % self.max_setup_rate
        ret += "\tTotal number of connections = %d\n" % self.total_connections
        ret += "\tSpeed scaling               = %s\n" % str(self.ss)
        return ret;

    def getPerSystem(self, count):
        msr = self.max_setup_rate / count
        cnn = self.total_connections / count
        return TestParameters(msr, cnn, self.ss);

    def getConnections(self):
        return self.total_connections;

class ProxDpiTester(Prox):
    TENGIGABITBYTESPERSECOND = 1250000000

    def __init__(self, ts, testParam, ID):
        super(ProxDpiTester, self).__init__(ts)

	self._sc = None
	self._lastTot = None
	self._prevTot = None;
	self._prevBytesClient = None
	self._lastBytesClient = None
	self._prevBytesTxMeassurement = None
	self._lastBytesTxMeassurement = None

	self._setDefaultArguments();
	self._setMsr(testParam.max_setup_rate)
	self._setConnections(testParam.total_connections);
	self._setSpeedScaling(testParam.ss);
	self._setID(ID);

    def _setDefaultArguments(self):
        self.addArgument("-e")
        self.addArgument("-t")
        self.addArgument("-k")
        self.addArgument("-d")
        self.addArgument("-r 0.01");

    def _setMsr(self, msr):
        self.addArgument("-q max_setup_rate=" + str(msr))

    def _setConnections(self, connections):
        self.addArgument("-q connections=" + str(connections))

    def _setID(self, ID):
        self.addArgument("-q test_system_id=" + str(ID))

    def _setSpeedScaling(self, ss):
        self.addArgument("-q ss=" + str(ss))

    def _querySetup2(self):
        self._query_client_ports();
        self._query_server_ports();
        self._query_cores();

    def _query_client_ports(self):
        self._client_ports = []
        for i in range(0, len(self._ports), 2):
            self._client_ports.append(self._ports[i]);

    def _query_server_ports(self):
        self._server_ports = []
        for i in range(1, len(self._ports), 2):
            self._server_ports.append(self._ports[i]);

    def _query_cores(self):
        self._query_ld();
        self._query_servers();
        self._query_clients();

    def _query_ld(self):
        self._ld = self._get_core_list("$all_ld");

    def _query_servers(self):
        self._servers = self._get_core_list("$all_servers")

    def _query_clients(self):
        self._clients = self._get_core_list("$all_clients")

    def _get_core_list(self, var):
        ret = []
        result = self._send("echo " + var)._recv();
        for e in result.split(","):
            ret += [e];
        return ret;

    def start_all_ld(self):
        self._send("start $all_ld");

    def start_all_workers(self):
        self._send("start $all_workers");

    def stop_all_ld(self):
        self._send("stop $all_ld");

    def stop_all_workers(self):
        self._send("stop $all_workers");

    def update_stats(self):
        if (self._sc is None):
            self._sc = StatsCmd(self)
            self._sc.add(self._buildTotalConnectionsCmd())
            self._sc.add(self._buildReTXCmd())
            self._sc.add(self._buildIerrorsCmd())
            self._sc.add(self._buildBytesPerPortCmd(self._client_ports, "rx"));

        self._sc.sendRecv()

        self._updateTotalConnections(self._sc.getResult(0))
        self._updateReTX(self._sc.getResult(1))
        self._updateIerrors(self._sc.getResult(2))
        self._update_rates_client_ports(self._sc.getResult(3));

    def _buildTotalConnectionsCmd(self):
        cmd = "l4gen(%s).tsc" % str(self._clients[0])

        for core in self._clients:
            if (len(cmd) > 0):
                cmd += ","
            cmd += "l4gen(%s).created,l4gen(%s).finished" % (str(core), str(core))
        return cmd;

    def _updateTotalConnections(self, rep):
        instant = Decimal(int(rep[0]) - self._beg)/self._hz
        rep = rep[1:]
        tot = 0;
        for i in range(0,len(rep), 2):
            tot += int(rep[i]) - int(rep[i + 1]);

        prev = self._lastTot;
        last = TimeSeriesPoint(tot, instant);

        if (prev == None):
            prev = last;

        self._prevTot = prev
        self._lastTot = last;

    def _buildReTXCmd(self):
        cmd = ""
        for core in self._clients + self._servers:
            if (len(cmd) > 0):
                cmd += ","
            cmd += "l4gen(%s).retx" % str(core)
        return cmd;

    def _updateReTX(self, rep):
        retx = 0;
        for i in rep:
            retx += int(i);
        self._retx = retx;

    def _updateIerrors(self, rep):
        self._ierrors = self._parseIerrorsReply(rep)

    def get_total_connections(self):
        return self._lastTot.getValue()

    def getCurrentSetupRate(self):
        return int(self._lastTot.getRateOfChange(self._prevTot));

    def get_total_retx(self):
        return self._retx

    def get_rates_client_ports(self):
        return self._calcLinkUtilization(self._prevBytesClient, self._lastBytesClient);

    def getIerrorsCached(self):
        return self._ierrors;

    def _update_rates_client_ports(self, rep):
        prevBytes = self._lastBytesClient
        lastBytes = self._parseTimeSeries(rep);

        if (prevBytes == None):
            prevBytes = lastBytes;

        self._prevBytesClient = prevBytes;
        self._lastBytesClient = lastBytes;

    def _getBytesPerPort(self, ports, rxOrTx):
        sc = StatsCmd(self);
        sc.add(self._buildBytesPerPortCmd(ports, rxOrTx))
        sc.sendRecv();

        rep = sc.getResult(0);

        return self._parseTimeSeries(rep);

    def _buildBytesPerPortCmd(self, ports, rxOrTx):
        cmd = ""
        for port in ports:
            if (len(cmd) > 0):
                cmd += ","
            cmd += "port(%s).%s.bytes,port(%s).tsc" % (str(port), rxOrTx, str(port));
        return cmd

    def tx_rate_meassurement(self):
        prev = self._lastBytesTxMeassurement
        last = self._getBytesPerPort(self._server_ports, "tx");

        if (prev == None):
            prev = last;

        self._prevBytesTxMeassurement = prev
        self._lastBytesTxMeassurement = last

        return self._calcLinkUtilization(prev, last);

    def _parseTimeSeries(self, rep):
        ret = []
        for i in range(0, len(rep), 2):
            val = int(rep[0])
            instant = Decimal(int(rep[1]) - self._beg)/self._hz
            ret.append(TimeSeriesPoint(val, instant));
        return ret;

    def _calcLinkUtilization(self, prev, last):
        ret = []
        for i in range(0, len(prev)):
            bytesPerSecond = last[i].getRateOfChange(prev[i]);
            linkFraction = Decimal(bytesPerSecond)/self.TENGIGABITBYTESPERSECOND
            ret.append(round(linkFraction,2));
        return ret;
