Files
2026-09-03 08:38:17 +08:00

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)