97 lines
4.6 KiB
Python
97 lines
4.6 KiB
Python
import collections
|
|
import concurrent.futures
|
|
import datetime
|
|
import hashlib
|
|
import http.client
|
|
import json
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import time
|
|
|
|
BASE = '/www/backup/txiaw-mysql-fix-20260831'
|
|
|
|
def probe_http(host, path, ua='Codex-Repair-Check/1.0'):
|
|
started = time.monotonic()
|
|
c = http.client.HTTPConnection('127.0.0.1', 80, timeout=10)
|
|
c.request('GET', path, headers={'Host': host, 'User-Agent': ua})
|
|
r = c.getresponse()
|
|
data = r.read()
|
|
result = {'host': host, 'path': path, 'status': r.status, 'bytes': len(data), 'seconds': round(time.monotonic() - started, 4)}
|
|
c.close()
|
|
return result
|
|
|
|
checks = []
|
|
for host, path, ua, expected in [
|
|
('www.txiaw.com', '/?r=codex-final-guard', '', [429]),
|
|
('www.txiaw.com', '/', 'Codex-Repair-Check/1.0', [200]),
|
|
('www.txiaw.com', '/down/184778.html', 'Codex-Repair-Check/1.0', [200]),
|
|
('www.xxiaw.com', '/', 'Codex-Repair-Check/1.0', [200]),
|
|
('m.bchongw.com', '/', 'Codex-Repair-Check/1.0', [200, 301, 302]),
|
|
]:
|
|
result = probe_http(host, path, ua)
|
|
checks.append(result)
|
|
if result['status'] not in expected:
|
|
raise RuntimeError('Page regression: ' + str(result))
|
|
print('PAGE_CHECKS', json.dumps(checks), flush=True)
|
|
|
|
# A bounded 30-request check verifies the limiter, not a stress test.
|
|
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as pool:
|
|
burst = list(pool.map(lambda i: probe_http('www.txiaw.com', '/?guard_test=' + str(i)), range(30)))
|
|
statuses = collections.Counter(p['status'] for p in burst)
|
|
if not statuses.get(429) or any(k not in [200, 429] for k in statuses):
|
|
raise RuntimeError('Homepage rate limiter did not behave as expected: ' + str(statuses))
|
|
unaffected = [probe_http('www.txiaw.com', '/down/184778.html'), probe_http('www.xxiaw.com', '/')]
|
|
if any(p['status'] != 200 for p in unaffected):
|
|
raise RuntimeError('Homepage limits affected unrelated pages: ' + str(unaffected))
|
|
time.sleep(5)
|
|
recovery = probe_http('www.txiaw.com', '/')
|
|
if recovery['status'] != 200:
|
|
raise RuntimeError('Rate limit did not recover: ' + str(recovery))
|
|
print('RATE_LIMIT_CHECK', dict(statuses), 'unaffected', json.dumps(unaffected), 'recovery', json.dumps(recovery), flush=True)
|
|
|
|
config = open('/www/wwwroot/www.txiaw.com/Runtime/Conf/config.php', encoding='utf-8').read()
|
|
cfg = {m.group(1).lower(): m.group(3) for m in re.finditer(r'''['"](db_[a-z_]+)['"]\s*=>\s*(['"])(.*?)\2''', config, re.I)}
|
|
env = dict(os.environ, MYSQL_PWD=cfg.get('db_pwd', cfg.get('db_password')))
|
|
mysql = ['/www/server/mysql/bin/mysql', '--no-defaults', '-u' + cfg['db_user'], '-h' + cfg['db_host'], '--connect-timeout=5', '-B']
|
|
|
|
def sql(q):
|
|
p = subprocess.run(mysql + ['-e', q], env=env, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, timeout=10)
|
|
if p.returncode:
|
|
raise RuntimeError(p.stderr[:500])
|
|
return p.stdout
|
|
|
|
query = "SHOW GLOBAL STATUS WHERE Variable_name IN ('Threads_connected','Threads_running','Slow_queries','Questions','Uptime','Innodb_row_lock_current_waits');"
|
|
a = sql(query)
|
|
started = time.monotonic()
|
|
time.sleep(5)
|
|
b = sql(query)
|
|
elapsed = time.monotonic() - started
|
|
|
|
def stats(raw):
|
|
return {p[0]: int(p[1]) for p in (line.split('\t') for line in raw.splitlines()[1:]) if len(p) == 2}
|
|
|
|
before, after = stats(a), stats(b)
|
|
per_second = {k: round((after[k] - before[k]) / elapsed, 3) for k in ['Slow_queries', 'Questions']}
|
|
print('MYSQL_STATUS', json.dumps(after), 'PER_SECOND', json.dumps(per_second), flush=True)
|
|
|
|
cpu = subprocess.check_output(['pidstat', '-u', '-p', '535252', '1', '3'], universal_newlines=True)
|
|
vm = subprocess.check_output(['vmstat', '1', '3'], universal_newlines=True)
|
|
up = subprocess.check_output(['uptime'], universal_newlines=True).strip()
|
|
print('UPTIME', up, flush=True)
|
|
print('MYSQL_CPU\n' + cpu, flush=True)
|
|
print('VMSTAT\n' + vm, flush=True)
|
|
|
|
lines = subprocess.check_output(['tail', '-n', '3000', '/www/wwwlogs/www.txiaw.com.log']).decode('utf-8', 'replace').splitlines()
|
|
log_status = collections.Counter()
|
|
for line in lines:
|
|
parts = line.split('"')
|
|
if len(parts) > 2 and parts[2].split():
|
|
log_status[parts[2].split()[0]] += 1
|
|
print('LATEST_LOG_STATUS_SAMPLE', dict(log_status), flush=True)
|
|
|
|
subprocess.run(['/www/server/nginx/sbin/nginx', '-t'], check=True, timeout=10)
|
|
result = {'time': datetime.datetime.now().isoformat(), 'page_checks': checks, 'burst_statuses': dict(statuses), 'unaffected_by_homepage_limit': unaffected, 'rate_limit_recovery': recovery, 'mysql_status': after, 'mysql_per_second': per_second, 'uptime': up, 'mysql_cpu': cpu, 'vmstat': vm, 'recent_access_status_sample': dict(log_status)}
|
|
json.dump(result, open(BASE + '/verification-result.json', 'w'), indent=2)
|
|
print('VERIFICATION_COMPLETE', flush=True)
|