更新
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user