From: Stefan Eissing Date: Fri, 15 Apr 2022 10:59:22 +0000 (+0000) Subject: *) test: core stress test_core_002 enhanved to monitor dynamic child X-Git-Tag: 2.5.0-alpha2-ci-test-only~389 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0e9d2a6c8a55729b5edc9b6288e1daa86ee56ac1;p=thirdparty%2Fapache%2Fhttpd.git *) test: core stress test_core_002 enhanved to monitor dynamic child changes on load and graceful reload of the server. git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1899885 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/test/modules/core/test_002_restarts.py b/test/modules/core/test_002_restarts.py index 375c7ca8e3e..cf203bc82db 100644 --- a/test/modules/core/test_002_restarts.py +++ b/test/modules/core/test_002_restarts.py @@ -1,4 +1,8 @@ import os +import re +import time +from datetime import datetime, timedelta +from threading import Thread import pytest @@ -6,50 +10,141 @@ from .env import CoreTestEnv from pyhttpd.conf import HttpdConf +class Loader: + + def __init__(self, env, url: str, clients: int, req_per_client: int = 10): + self.env = env + self.url = url + self.clients = clients + self.req_per_client = req_per_client + self.result = None + self.total_request = 0 + self._thread = None + + def run(self): + self.total_requests = self.clients * self.req_per_client + conn_per_client = 5 + args = [self.env.h2load, f"--connect-to=localhost:{self.env.https_port}", + "--h1", # use only http/1.1 + "-n", str(self.total_requests), # total # of requests to make + "-c", str(conn_per_client * self.clients), # total # of connections to make + "-r", str(self.clients), # connections at a time + "--rate-period", "2", # create conns every 2 sec + self.url, + ] + self.result = self.env.run(args) + + def start(self): + self._thread = Thread(target=self.run) + self._thread.start() + + def join(self): + self._thread.join() + + +class ChildDynamics: + + RE_DATE_TIME = re.compile(r'\[(?P[^\]]+)\] .*') + RE_TIME_FRAC = re.compile(r'(?P
.* \d\d:\d\d:\d\d)(?P.(?P.\d+)) (?P\d+)') + RE_CHILD_CHANGE = re.compile(r'\[(?P[^\]]+)\] ' + r'\[mpm_event:\w+\]' + r' \[pid (?P\d+):tid \w+\] ' + r'.* Child (?P\d+) (?P\w+): ' + r'pid (?P\d+), gen (?P\d+), .*') + + def __init__(self, env: CoreTestEnv): + self.env = env + self.changes = list() + self._start = None + for l in open(env.httpd_error_log.path): + m = self.RE_CHILD_CHANGE.match(l) + if m: + self.changes.append({ + 'pid': int(m.group('pid')), + 'child_no': int(m.group('child_no')), + 'gen': int(m.group('generation')), + 'action': m.group('action'), + 'rtime' : self._rtime(m.group('date_time')) + }) + continue + if self._start is None: + m = self.RE_DATE_TIME.match(l) + if m: + self._rtime(m.group('date_time')) + + def _rtime(self, s: str) -> timedelta: + micros = 0 + m = self.RE_TIME_FRAC.match(s) + if m: + micros = int(m.group('micros')) + s = f"{m.group('dt')} {m.group('year')}" + d = datetime.strptime(s, '%a %b %d %H:%M:%S %Y') + timedelta(microseconds=micros) + if self._start is None: + self._start = d + delta = d - self._start + return f"{delta.seconds:+02d}.{delta.microseconds:06d}" + + + @pytest.mark.skipif(condition='STRESS_TEST' not in os.environ, reason="STRESS_TEST not set in env") @pytest.mark.skipif(condition=not CoreTestEnv().h2load_is_at_least('1.41.0'), reason="h2load unavailable or misses --connect-to option") class TestRestarts: - @pytest.fixture(autouse=True, scope='class') - def _class_scope(self, env): + def test_core_002_01(self, env): + # Lets make a tight config that triggers dynamic child behaviour conf = HttpdConf(env, extras={ 'base': f""" -StartServers 1 -ServerLimit 3 -ThreadLimit 4 -ThreadsPerChild 4 -MinSpareThreads 4 -MaxSpareThreads 6 -MaxRequestWorkers 12 -MaxConnectionsPerChild 0 - -LogLevel mpm_event:trace6 - """, + StartServers 1 + ServerLimit 3 + ThreadLimit 4 + ThreadsPerChild 4 + MinSpareThreads 4 + MaxSpareThreads 6 + MaxRequestWorkers 12 + MaxConnectionsPerChild 0 + + LogLevel mpm_event:trace6 + """, }) conf.add_vhost_cgi() conf.install() + + # clear logs and start server, start load + env.httpd_error_log.clear_log() assert env.apache_restart() == 0 + # we should see a single child started + cd = ChildDynamics(env) + assert len(cd.changes) == 1, f"{cd.changes}" + assert cd.changes[0]['action'] == 'started' + # This loader simulates 6 clients, each making 10 requests. + # delay.py sleeps for 1sec, so this should run for about 10 seconds + loader = Loader(env=env, url=env.mkurl("https", "cgi", "/delay.py"), + clients=6, req_per_client=10) + loader.start() + # Expect 2 more children to have been started after half time + time.sleep(5) + cd = ChildDynamics(env) + assert len(cd.changes) == 3, f"{cd.changes}" + assert len([x for x in cd.changes if x['action'] == 'started']) == 3, f"{cd.changes}" - def test_core_002_01(self, env): - clients = 6 - total_requests = clients * 10 - conn_per_client = 5 - url = env.mkurl("https", "cgi", "/delay.py") - args = [env.h2load, f"--connect-to=localhost:{env.https_port}", - "--h1", # use only http/1.1 - "-n", str(total_requests), # total # of requests to make - "-c", str(conn_per_client * clients), # total # of connections to make - "-r", str(clients), # connections at a time - "--rate-period", "2", # create conns every 2 sec - url, - ] - r = env.run(args) - assert 0 == r.exit_code - r = env.h2load_status(r) - assert r.results["h2load"]["requests"] == { - "total": total_requests, "started": total_requests, - "done": total_requests, "succeeded": total_requests - }, f"{r.stdout}" + # Trigger a server reload + assert env.apache_reload() == 0 + # a graceful reload lets ongoing requests continue, but + # after a while all gen 0 children should have stopped + time.sleep(3) # FIXME: this pbly depends on the runtime a lot, do we have expectations? + cd = ChildDynamics(env) + gen0 = [x for x in cd.changes if x['gen'] == 0] + assert len([x for x in gen0 if x['action'] == 'stopped']) == 3 + + # wait for the loader to finish and stop the server + loader.join() + env.apache_stop() + # Similar to before the reload, we expect 3 children to have + # been started and stopped again on server stop + cd = ChildDynamics(env) + gen1 = [x for x in cd.changes if x['gen'] == 1] + assert len([x for x in gen1 if x['action'] == 'started']) == 3 + assert len([x for x in gen1 if x['action'] == 'stopped']) == 3