summaryrefslogtreecommitdiffstats
path: root/configure
blob: 978b71e5c61bcacf44486984c266d27733c53798 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#!/usr/bin/env bash
set -o pipefail -o errtrace -o nounset -o errexit

# Experimental script, please consult with dmarion@me.com before
# submitting any changes

# defaults
build_dir=.
install_dir=/usr/local
build_type=release
prefix_path=/opt/vpp/external/$(uname -m)/
src_dir="$(dirname "$(readlink -f "$0")")"

help()
{
  cat << __EOF__
VPP Build Configuration Script

USAGE: ${0} [options]

OPTIONS:
  --help, -h              This help
  --build-dir, -b         Build directory
  --install-dir, -i       Install directory
  --build-type, -t        Build type (release, debug, ...)
  --wipe, -w              Wipe whole repo (except startup.* files)
__EOF__
}

while (( "$#" )); do
  case "$1" in
    -h|--help)
      help
      exit 1
      ;;
    -b|--build-dir)
      if [ -n "$2" ] && [ ${2:0:1} != "-" ]; then
        build_dir=$2
        shift 2
      else
        echo "Error: Argument for $1 is missing" >&2
        exit 1
      fi
      ;;
    -i|--install-dir)
      if [ -n "$2" ] && [ ${2:0:1} != "-" ]; then
        install_dir=$2
        shift 2
      else
        echo "Error: Argument for $1 is missing" >&2
        exit 1
      fi
      ;;
    -t|--build-type)
      if [ -n "$2" ] && [ ${2:0:1} != "-" ]; then
        build_type=$2
        shift 2
      else
        echo "Error: Argument for $1 is missing" >&2
        exit 1
      fi
      ;;
    -w|--wipe)
      git clean -fdx --exclude=startup.\*
      exit 1
      ;;
    -*|--*=) # unsupported flags
      echo "Error: Unsupported flag $1" >&2
      exit 1
      ;;
    *) # preserve positional arguments
      PARAMS="$PARAMS $1"
      shift
      ;;
  esac
done

cmake \
  -G Ninja \
  -S ${src_dir}/src \
  -B ${build_dir} \
  -DCMAKE_PREFIX_PATH=${prefix_path} \
  -DCMAKE_INSTALL_PREFIX=${install_dir} \
  -DCMAKE_EXPORT_COMPILE_COMMANDS:BOOL=ON \
  -DCMAKE_BUILD_TYPE:STRING=${build_type}

  cat << __EOF__

  Useful build commands:

  ninja                   Build VPP
  ninja set-build-type-*  Change build type to <debug|release|gcov|...>
  ninja config            Start build configuration TUI
  ninja run               Runs VPP using startup.conf in the build directory
  ninja debug             Runs VPP inside GDB using startup.conf in the build directory
  ninja pkg-deb           Create .deb packages
  ninja install           Install VPP to $install_dir

__EOF__
highlight .ch { color: #888888 } /* Comment.Hashbang */ .highlight .cm { color: #888888 } /* Comment.Multiline */ .highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */ .highlight .cpf { color: #888888 } /* Comment.PreprocFile */ .highlight .c1 { color: #888888 } /* Comment.Single */ .highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */ .highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ .highlight .ge { font-style: italic } /* Generic.Emph */ .highlight .gr { color: #aa0000 } /* Generic.Error */ .highlight .gh { color: #333333 } /* Generic.Heading */ .highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ .highlight .go { color: #888888 } /* Generic.Output */ .highlight .gp { color: #555555 } /* Generic.Prompt */ .highlight .gs { font-weight: bold } /* Generic.Strong */ .highlight .gu { color: #666666 } /* Generic.Subheading */ .highlight .gt { color: #aa0000 } /* Generic.Traceback */ .highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */ .highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */ .highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */ .highlight .kp { color: #008800 } /* Keyword.Pseudo */ .highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */ .highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */ .highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */ .highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */ .highlight .na { color: #336699 } /* Name.Attribute */ .highlight .nb { color: #003388 } /* Name.Builtin */ .highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */ .highlight .no { color: #003366; font-weight: bold } /* Name.Constant */ .highlight .nd { color: #555555 } /* Name.Decorator */ .highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */ .highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */ .highlight .nl { color: #336699; font-style: italic } /* Name.Label */ .highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */ .highlight .py { color: #336699; font-weight: bold } /* Name.Property */ .highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */ .highlight .nv { color: #336699 } /* Name.Variable */ .highlight .ow { color: #008800 } /* Operator.Word */ .highlight .w { color: #bbbbbb } /* Text.Whitespace */ .highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */ .highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */ .highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */ .highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */ .highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */ .highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */ .highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */ .highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */ .highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */ .highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */ .highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */ .highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */ .highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */ .highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */ .highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */ .highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */ .highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */ .highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */ .highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */ .highlight .vc { color: #336699 } /* Name.Variable.Class */ .highlight .vg { color: #dd7700 } /* Name.Variable.Global */ .highlight .vi { color: #3333bb } /* Name.Variable.Instance */ .highlight .vm { color: #336699 } /* Name.Variable.Magic */ .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */ }
# Copyright (c) 2019 Cisco and/or its affiliates.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at:
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""wrk implementation into CSIT framework.
"""

import re

from copy import deepcopy
from time import sleep

from robot.api import logger

from resources.libraries.python.ssh import SSH
from resources.libraries.python.topology import NodeType
from resources.libraries.python.CpuUtils import CpuUtils
from resources.libraries.python.Constants import Constants

from resources.tools.wrk.wrk_traffic_profile_parser import WrkTrafficProfile
from resources.tools.wrk.wrk_errors import WrkError


REGEX_LATENCY_STATS = \
    r"Latency\s*" \
    r"(\d*\.*\d*\S*)\s*" \
    r"(\d*\.*\d*\S*)\s*" \
    r"(\d*\.*\d*\S*)\s*" \
    r"(\d*\.*\d*\%)"
REGEX_RPS_STATS = \
    r"Req/Sec\s*" \
    r"(\d*\.*\d*\S*)\s*" \
    r"(\d*\.*\d*\S*)\s*" \
    r"(\d*\.*\d*\S*)\s*" \
    r"(\d*\.*\d*\%)"
REGEX_RPS = r"Requests/sec:\s*" \
            r"(\d*\.*\S*)"
REGEX_BW = r"Transfer/sec:\s*" \
           r"(\d*\.*\S*)"
REGEX_LATENCY_DIST = \
    r"Latency Distribution\n" \
    r"\s*50\%\s*(\d*\.*\d*\D*)\n" \
    r"\s*75\%\s*(\d*\.*\d*\D*)\n" \
    r"\s*90\%\s*(\d*\.*\d*\D*)\n" \
    r"\s*99\%\s*(\d*\.*\d*\D*)\n"

# Split number and multiplicand, e.g. 14.25k --> 14.25 and k
REGEX_NUM = r"(\d*\.*\d*)(\D*)"


def check_wrk(tg_node):
    """Check if wrk is installed on the TG node.

    :param tg_node: Traffic generator node.
    :type tg_node: dict
    :raises: RuntimeError if the given node is not a TG node or if the
        command is not availble.
    """

    if tg_node['type'] != NodeType.TG:
        raise RuntimeError('Node type is not a TG.')

    ssh = SSH()
    ssh.connect(tg_node)

    ret, _, _ = ssh.exec_command(
        "sudo -E "
        "sh -c '{0}/resources/tools/wrk/wrk_utils.sh installed'".
        format(Constants.REMOTE_FW_DIR))
    if int(ret) != 0:
        raise RuntimeError('WRK is not installed on TG node.')


def run_wrk(tg_node, profile_name, tg_numa, test_type, warm_up=False):
    """Send the traffic as defined in the profile.

    :param tg_node: Traffic generator node.
    :param profile_name: The name of wrk traffic profile.
    :param tg_numa: Numa node on which wrk will run.
    :param test_type: The type of the tests: cps, rps, bw
    :param warm_up: If True, warm-up traffic is generated before test traffic.
    :type profile_name: str
    :type tg_node: dict
    :type tg_numa: int
    :type test_type: str
    :type warm_up: bool
    :returns: Message with measured data.
    :rtype: str
    :raises: RuntimeError if node type is not a TG.
    """

    if tg_node['type'] != NodeType.TG:
        raise RuntimeError('Node type is not a TG.')

    # Parse and validate the profile
    profile_path = ("resources/traffic_profiles/wrk/{0}.yaml".
                    format(profile_name))
    profile = WrkTrafficProfile(profile_path).traffic_profile

    cores = CpuUtils.cpu_list_per_node(tg_node, tg_numa)
    first_cpu = cores[profile["first-cpu"]]

    if len(profile["urls"]) == 1 and profile["cpus"] == 1:
        params = [
            "traffic_1_url_1_core",
            str(first_cpu),
            str(profile["nr-of-threads"]),
            str(profile["nr-of-connections"]),
            "{0}s".format(profile["duration"]),
            "'{0}'".format(profile["header"]),
            str(profile["timeout"]),
            str(profile["script"]),
            str(profile["latency"]),
            "'{0}'".format(" ".join(profile["urls"]))
        ]
        if warm_up:
            warm_up_params = deepcopy(params)
            warm_up_params[4] = "10s"
    elif len(profile["urls"]) == profile["cpus"]:
        params = [
            "traffic_n_urls_n_cores",
            str(first_cpu),
            str(profile["nr-of-threads"]),
            str(profile["nr-of-connections"]),
            "{0}s".format(profile["duration"]),
            "'{0}'".format(profile["header"]),
            str(profile["timeout"]),
            str(profile["script"]),
            str(profile["latency"]),
            "'{0}'".format(" ".join(profile["urls"]))
        ]
        if warm_up:
            warm_up_params = deepcopy(params)
            warm_up_params[4] = "10s"
    else:
        params = [
            "traffic_n_urls_m_cores",
            str(first_cpu),
            str(profile["cpus"] / len(profile["urls"])),
            str(profile["nr-of-threads"]),
            str(profile["nr-of-connections"]),
            "{0}s".format(profile["duration"]),
            "'{0}'".format(profile["header"]),
            str(profile["timeout"]),
            str(profile["script"]),
            str(profile["latency"]),
            "'{0}'".format(" ".join(profile["urls"]))
        ]
        if warm_up:
            warm_up_params = deepcopy(params)
            warm_up_params[5] = "10s"

    args = " ".join(params)

    ssh = SSH()
    ssh.connect(tg_node)

    if warm_up:
        warm_up_args = " ".join(warm_up_params)
        ret, _, _ = ssh.exec_command(
            "{0}/resources/tools/wrk/wrk_utils.sh {1}".
            format(Constants.REMOTE_FW_DIR, warm_up_args), timeout=1800)
        if int(ret) != 0:
            raise RuntimeError('wrk runtime error.')
        sleep(60)

    ret, stdout, _ = ssh.exec_command(
        "{0}/resources/tools/wrk/wrk_utils.sh {1}".
        format(Constants.REMOTE_FW_DIR, args), timeout=1800)
    if int(ret) != 0:
        raise RuntimeError('wrk runtime error.')

    stats = _parse_wrk_output(stdout)

    log_msg = "\nMeasured values:\n"
    if test_type == "cps":
        log_msg += "Connections/sec: Avg / Stdev / Max  / +/- Stdev\n"
        for item in stats["rps-stats-lst"]:
            log_msg += "{0} / {1} / {2} / {3}\n".format(*item)
        log_msg += "Total cps: {0}cps\n".format(stats["rps-sum"])
    elif test_type == "rps":
        log_msg += "Requests/sec: Avg / Stdev / Max  / +/- Stdev\n"
        for item in stats["rps-stats-lst"]:
            log_msg += "{0} / {1} / {2} / {3}\n".format(*item)
        log_msg += "Total rps: {0}rps\n".format(stats["rps-sum"])
    elif test_type == "bw":
        log_msg += "Transfer/sec: {0}Bps".format(stats["bw-sum"])

    logger.info(log_msg)

    return log_msg


def _parse_wrk_output(msg):
    """Parse the wrk stdout with the results.

    :param msg: stdout of wrk.
    :type msg: str
    :returns: Parsed results.
    :rtype: dict
    :raises: WrkError if the message does not include the results.
    """

    if "Thread Stats" not in msg:
        raise WrkError("The output of wrk does not include the results.")

    msg_lst = msg.splitlines(False)

    stats = {
        "latency-dist-lst": list(),
        "latency-stats-lst": list(),
        "rps-stats-lst": list(),
        "rps-lst": list(),
        "bw-lst": list(),
        "rps-sum": 0,
        "bw-sum": None
    }

    for line in msg_lst:
        if "Latency Distribution" in line:
            # Latency distribution - 50%, 75%, 90%, 99%
            pass
        elif "Latency" in line:
            # Latency statistics - Avg, Stdev, Max, +/- Stdev
            pass
        elif "Req/Sec" in line:
            # rps statistics - Avg, Stdev, Max, +/- Stdev
            stats["rps-stats-lst"].append((
                _evaluate_number(re.search(REGEX_RPS_STATS, line).group(1)),
                _evaluate_number(re.search(REGEX_RPS_STATS, line).group(2)),
                _evaluate_number(re.search(REGEX_RPS_STATS, line).group(3)),
                _evaluate_number(re.search(REGEX_RPS_STATS, line).group(4))))
        elif "Requests/sec:" in line:
            # rps (cps)
            stats["rps-lst"].append(
                _evaluate_number(re.search(REGEX_RPS, line).group(1)))
        elif "Transfer/sec:" in line:
            # BW
            stats["bw-lst"].append(
                _evaluate_number(re.search(REGEX_BW, line).group(1)))

    for item in stats["rps-stats-lst"]:
        stats["rps-sum"] += item[0]
    stats["bw-sum"] = sum(stats["bw-lst"])

    return stats


def _evaluate_number(num):
    """Evaluate the numeric value of the number with multiplicands, e.g.:
    12.25k --> 12250

    :param num: Number to evaluate.
    :type num: str
    :returns: Evaluated number.
    :rtype: float
    :raises: WrkError if it is not possible to evaluate the given number.
    """

    val = re.search(REGEX_NUM, num)
    try:
        val_num = float(val.group(1))
    except ValueError:
        raise WrkError("The output of wrk does not include the results "
                       "or the format of results has changed.")
    val_mul = val.group(2).lower()
    if val_mul:
        if "k" in val_mul:
            val_num *= 1000
        elif "m" in val_mul:
            val_num *= 1000000
        elif "g" in val_mul:
            val_num *= 1000000000
        elif "b" in val_mul:
            pass
        elif "%" in val_mul:
            pass
        elif "" in val_mul:
            pass
        else:
            raise WrkError("The multiplicand {0} is not defined.".
                           format(val_mul))
    return val_num