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)