Add load test harness for pool and fleet sizing
Adds the scripts used to size the connection pool and validate fleet polling under load: burst arrival and mixed workload drivers, pool and resource samplers, a UI probe, host seed/drop SQL, and a gevent Dockerfile. Hosts are addressed by compose service name (api:5001) rather than a local path, so the harness runs against any stack rather than one machine. loadtest_token.py mints a scoped collector token named LOADTEST and prints the secret once, then revokes only that token by name so a real collector token is never touched. envkeyfallback is false, so the harness needs a genuine managed token rather than an env key.
This commit is contained in:
68
scripts/loadtest/sample_dbconns.py
Normal file
68
scripts/loadtest/sample_dbconns.py
Normal file
@@ -0,0 +1,68 @@
|
||||
"""Sample live MySQL connection counts at high frequency.
|
||||
|
||||
WHY: pool pressure from a long-poll fleet is BURSTY. The route releases its
|
||||
connection before sleeping and re-acquires on wake, so the interesting number is
|
||||
the peak concurrent checkout during a wake burst, not the steady-state count. A
|
||||
slow sampler misses it entirely.
|
||||
|
||||
Runs on the HOST (needs the docker CLI).
|
||||
|
||||
Usage:
|
||||
python sample_dbconns.py --seconds 120 --interval 0.25
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
import statistics
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
QUERY = ("SELECT COUNT(*), SUM(command<>'Sleep') "
|
||||
"FROM information_schema.processlist WHERE user='shopdb';")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--container', default='shopdb-flask-wilm-db-1')
|
||||
parser.add_argument('--seconds', type=int, default=120)
|
||||
parser.add_argument('--interval', type=float, default=0.25)
|
||||
arguments = parser.parse_args()
|
||||
|
||||
total_samples = []
|
||||
active_samples = []
|
||||
deadline = time.monotonic() + arguments.seconds
|
||||
command = ('mysql -u root -p$MYSQL_ROOT_PASSWORD -N -B -e "%s"' % QUERY)
|
||||
|
||||
while time.monotonic() < deadline:
|
||||
try:
|
||||
output = subprocess.check_output(
|
||||
['docker', 'exec', arguments.container, 'sh', '-c', command],
|
||||
stderr=subprocess.DEVNULL, text=True).strip()
|
||||
except subprocess.CalledProcessError:
|
||||
break
|
||||
if not output:
|
||||
break
|
||||
parts = output.split()
|
||||
total_samples.append(int(parts[0]))
|
||||
active_samples.append(int(parts[1]) if parts[1] != 'NULL' else 0)
|
||||
time.sleep(arguments.interval)
|
||||
|
||||
def summarize(values):
|
||||
if not values:
|
||||
return {'samples': 0}
|
||||
ordered = sorted(values)
|
||||
index = min(int(len(ordered) * 0.95), len(ordered) - 1)
|
||||
return {
|
||||
'samples': len(values),
|
||||
'median': statistics.median(values),
|
||||
'p95': ordered[index],
|
||||
'max': max(values),
|
||||
}
|
||||
|
||||
print(json.dumps({
|
||||
'connections': summarize(total_samples),
|
||||
'active_queries': summarize(active_samples),
|
||||
}, indent=2))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user