Hey Deprecated

Use rakyll/hey to test a web application with a load of requests.

python · Developer Tools

Source

import demistomock as demisto  # noqa: F401
from CommonServerPython import *  # noqa: F401
import re
import subprocess

# ---------- CONSTANTS ---------- #

FLOAT_RE = r'\d+\.\d+'
INT_RE = r'\d+'
BYTES_RE = r'\d+ bytes'


# ---------- HELPER FUNCTIONS ---------- #


def try_re(pattern: str, string: str, i: int = 0) -> Optional[Any]:
    re_res = re.findall(pattern, string)
    if len(re_res) == i + 1:  # ignore cases where len is greater than expected
        return re_res[i]
    return None


def name_value_arg_to_dict(arg: Optional[str]):
    parsed_input: dict[str, str] = {}
    if arg:
        args = argToList(arg)
        for item in args:
            if isinstance(item, str):
                key_val = item.split('=')
                if len(key_val) <= 1:
                    raise DemistoException(f'Invalid arg provided {item}. expected comma separated list of "key=value"')
                else:
                    key = key_val[0]
                    val = item[len(key) + 1:]
                    parsed_input[key] = val
    return parsed_input


def construct_hey_query(url: str,
                        requests_number: Optional[str] = None,
                        timeout: Optional[str] = None,
                        concurrency: Optional[str] = None,
                        duration: Optional[str] = None,
                        method: Optional[str] = None,
                        disable_compression: Optional[str] = None,
                        headers: Optional[str] = None,
                        body: Optional[str] = None,
                        proxy: Optional[str] = None,
                        enable_http2: Optional[str] = None,
                        disable_redirects: Optional[str] = None):
    hey_map = assign_params(
        t=timeout,
        n=requests_number,
        c=concurrency,
        m=method,
        z=duration + 's' if duration else None,
        d=body,
        x=proxy
    )

    query_args = ["hey"]

    if disable_compression == 'true':
        query_args.append('--disable-compression')
    if enable_http2 == 'true':
        query_args.append('-h2')
    if disable_redirects == 'true':
        query_args.append('-disable-redirects')
    if headers:
        for header_key, header_val in name_value_arg_to_dict(headers).items():
            query_args.extend(('-H', f'{header_key}:{header_val}'))
    for k, v in hey_map.items():
        query_args.extend([f"-{k}", v])
    query_args.append(url)

    return hey_map, query_args


# ---------- CLASSES ---------- #


class HeyPerformanceResult:
    def __init__(self,
                 result: str,
                 results_map: Optional[str],
                 t: Optional[str] = None,
                 c: Optional[str] = None,
                 n: Optional[str] = None,
                 z: Optional[str] = None,
                 **args):
        self._t = int(t or 20)
        self._c = int(c or 50)
        self._z = int(z[:-1]) if z else None  # remove 's' from z before parsing int
        _n = int(n or 200)
        self._n = _n - (_n % self._c)  # remove c remainder
        self._result = result or ''
        self._ext_outputs = name_value_arg_to_dict(results_map)

    def _get_summary(self, result: List[str]) -> tuple[dict, int]:
        """Returns summary dictionary and index after the summary"""
        summary = {}
        i = 0
        for i in range(len(result)):
            if 'Summary' in result[i]:
                continue
            if 'Response' in result[i]:
                break
            if 'Total:' in result[i]:
                if self._z:
                    continue
                total_time = try_re(FLOAT_RE, result[i])
                if total_time:
                    total_time = float(total_time) / self._c
                summary['TotalTime'] = total_time
            if 'Slowest' in result[i]:
                summary['SlowestTime'] = try_re(FLOAT_RE, result[i])
            if 'Fastest' in result[i]:
                summary['FastestTime'] = try_re(FLOAT_RE, result[i])
            if 'Average' in result[i]:
                summary['AverageTime'] = try_re(FLOAT_RE, result[i])
            if 'Requests' in result[i]:
                summary['RequestsPerSecond'] = try_re(FLOAT_RE, result[i])
            if 'Total data' in result[i]:
                summary['TotalData'] = try_re(BYTES_RE, result[i])
            if 'Size' in result[i]:
                summary['SizePerRequest'] = try_re(BYTES_RE, result[i])
        return summary, i

    @staticmethod
    def _get_successful_responses(result: List[str], result_i: int) -> int:
        """Returns number of successful responses in the result"""
        for i in range(result_i, len(result)):
            if '[200]' in result[i]:
                return try_re(INT_RE, result[i], 1) or 0
        return 0

    def human_readable_to_outputs(self):
        result = [line for line in self._result.split('\n') if line.strip() != '']
        outputs, response_i = self._get_summary(result)
        outputs['SuccessfulResponses'] = self._get_successful_responses(result, response_i)
        outputs.update({
            "TimeoutPerRequest": self._t,
            "Concurrency": self._c,
            "Requests": self._n,
        })
        if self._ext_outputs:
            outputs.update(self._ext_outputs)
        return outputs

    def to_results(self) -> CommandResults:
        outputs = self.human_readable_to_outputs()
        if self._z:
            outputs['TotalTime'] = self._z
        return CommandResults(outputs=outputs, outputs_prefix="Hey", readable_output=self._result)


def run_hey_test(url: str,
                 requests_number: Optional[str] = None,
                 timeout: Optional[str] = None,
                 concurrency: Optional[str] = None,
                 duration: Optional[str] = None,
                 method: Optional[str] = None,
                 disable_compression: Optional[str] = None,
                 results_map: Optional[str] = None,
                 headers: Optional[str] = None,
                 body: Optional[str] = None,
                 proxy: Optional[str] = None,
                 enable_http2: Optional[str] = None,
                 disable_redirects: Optional[str] = None,
                 *_args, **_kwargs) -> CommandResults:
    hey_map, hey_query = construct_hey_query(url,
                                             requests_number,
                                             timeout,
                                             concurrency,
                                             duration,
                                             method,
                                             disable_compression,
                                             headers,
                                             body,
                                             proxy,
                                             enable_http2,
                                             disable_redirects)
    result = subprocess.check_output(hey_query, stderr=subprocess.STDOUT, text=True)
    return HeyPerformanceResult(result=result, results_map=results_map, **hey_map).to_results()


def main() -> None:  # pragma: no cover
    args = demisto.args()
    try:
        return_results(run_hey_test(**args))
    # Log exceptions and return errors
    except Exception as e:
        return_error(f'Error:\n{str(e)}')


if __name__ in ('__main__', '__builtin__', 'builtins'):
    main()

README

Use rakyll/hey to test a web application with a load of requests.

Inputs


Argument Name Description
url URL to query.
requests_number Number of requests to run. Default is 200.
timeout Timeout for each request in seconds. Default is 20, use 0 for infinite.
concurrency Number of workers to run concurrently.
duration Duration of application to send requests (in seconds).
method HTTP method.
headers Custom HTTP header. Comma separated list of “key=value”. e.g. Type=IP,Size=100.
disable_compression Disable compression.
results_map Additional information to add to the result. Comma separated list of “key=value”. e.g. Content-Type=text/plain,Accept=*/*.
body HTTP request body.
proxy HTTP Proxy address as host:port.
enable_http2 Enable HTTP/2.
disable_redirects Disable following of HTTP redirects.

Outputs


Path Description Type
Hey.Timeout Timeout for each request. number
Hey.Concurrency Max number of concurrent workers. number
Hey.Requests Number of requests sent to URL. number
Hey.SlowestTime The slowest time it took for a request to finish. number
Hey.FastestTime The fastest time it took for a request to finish. number
Hey.SuccessfulResponses The number of responses that returned with 200 status code. number
Hey.AverageTime The average time it took for a request to finish. number
Hey.Result The full result in text format when output is set to “human_readable” number