"""Synthetic before/after benchmark; no live client, config, keys or conversations.""" from contextlib import closing import gc import hashlib import importlib.util import json from pathlib import Path import sqlite3 import statistics import sys import tempfile import time import tracemalloc from unittest import mock PROJECT=Path('C:/wechat_rpa') sys.path.insert(0,str(PROJECT)) import wecom_native_sender as sender import wxwork_db as current_db old_path=PROJECT/'backups/loading-performance-20260917/wxwork_db.py' spec=importlib.util.spec_from_file_location('wxwork_db_before_probe_optimization',old_path) old_db=importlib.util.module_from_spec(spec) spec.loader.exec_module(old_db) def measured(callback): gc.collect() tracemalloc.start() started=time.perf_counter() result=callback() elapsed=(time.perf_counter()-started)*1000 peak=tracemalloc.get_traced_memory()[1] tracemalloc.stop() return {'elapsedMs':round(elapsed,3),'pythonPeakBytes':peak,**result} report={'synthetic':True,'liveProcessesInspected':False,'messagesSent':0, 'note':'Component benchmarks on deterministic synthetic files, not measured end-to-end production latency.'} with tempfile.TemporaryDirectory() as temporary: root=Path(temporary) executable=root/'synthetic-WXWork.exe' block=(b'synthetic build image\0'*(1048576//22+1))[:1048576] with executable.open('wb') as handle: for _ in range(64):handle.write(block) size=executable.stat().st_size def hash_before(): for _ in range(2):hashlib.sha256(executable.read_bytes()).hexdigest() return {'hashReads':2,'bytesHashed':size*2} def hash_after(): with mock.patch.object(sender,'_process_creation_id',return_value=123),sender.readonly_build_validation_scope() as stats: for _ in range(2):sender._client_file_digest(executable,7,None,readonly=True) return {'hashReads':stats['hashedFiles'],'bytesHashed':stats['bytesHashed'],'hashReuse':stats['reusedFiles']} before=[];after=[] for index in range(5): if index%2: after.append(measured(hash_after));before.append(measured(hash_before)) else: before.append(measured(hash_before));after.append(measured(hash_after)) report['buildValidation']={'fileBytes':size,'beforeSamples':before,'afterSamples':after, 'beforeMedianMs':statistics.median(row['elapsedMs'] for row in before), 'afterMedianMs':statistics.median(row['elapsedMs'] for row in after), 'beforeMedianPeakBytes':statistics.median(row['pythonPeakBytes'] for row in before), 'afterMedianPeakBytes':statistics.median(row['pythonPeakBytes'] for row in after)} source=root/'WXWork' accounts=[str(100+index) for index in range(4)] for account in accounts: data=source/account/'Data';data.mkdir(parents=True) for name in current_db._RELEVANT_DBS: with closing(sqlite3.connect(data/name)) as connection: if name=='message.db': connection.execute('CREATE TABLE message_table(sender_id TEXT,conversation_id TEXT,content_type INT,send_time INT,content TEXT)') connection.executemany('INSERT INTO message_table VALUES(?,?,?,?,?)', (('200','S:'+account+'_200',2,index,'合成测试消息 '*8) for index in range(5000))) elif name=='user.db': connection.execute('CREATE TABLE user_table(id TEXT,name TEXT,real_name TEXT,account TEXT)') connection.executemany('INSERT INTO user_table VALUES(?,?,?,?)', ((str(index),'合成客户'+str(index),'',account) for index in range(15000))) elif name=='session.db': connection.execute('CREATE TABLE conversation_table(id TEXT,name TEXT,roomname_remark TEXT,session_id TEXT)') connection.executemany('INSERT INTO conversation_table VALUES(?,?,?,?)', (('S:'+account+'_'+str(index),'合成会话'+str(index),'','') for index in range(3000))) else: connection.execute('CREATE TABLE synthetic_payload(payload BLOB)') connection.execute('INSERT INTO synthetic_payload VALUES(zeroblob(262144))') connection.commit() def database_run(module,cache,scoped): options={'account':'100','load_metadata':False} if scoped else {} database=module.WXWorkDB(str(source),{},str(cache),**options) try: result={'currentAccountHealthy':database.health_check(account='100'), 'openedAccounts':len(database._conns),'loadedContacts':len(database.user_cache), 'preparedFiles':len(list(cache.rglob('*.db'))), 'preparedBytes':sum(path.stat().st_size for path in cache.rglob('*.db'))} finally:database.close() assert result['currentAccountHealthy'] return result before=[];after=[] for index in range(3): before.append(measured(lambda:database_run(old_db,root/f'before-cache-{index}',False))) after.append(measured(lambda:database_run(current_db,root/f'after-cache-{index}',True))) report['databasePreparation']={'accounts':4,'sourceFiles':24,'contacts':60000,'sourceMessages':20000, 'beforeSamples':before,'afterSamples':after, 'beforeMedianMs':statistics.median(row['elapsedMs'] for row in before), 'afterMedianMs':statistics.median(row['elapsedMs'] for row in after), 'beforeMedianPeakBytes':statistics.median(row['pythonPeakBytes'] for row in before), 'afterMedianPeakBytes':statistics.median(row['pythonPeakBytes'] for row in after)} output=Path(__file__).with_name('PROTOCOL_PERFORMANCE.json') output.write_text(json.dumps(report,ensure_ascii=False,indent=2),encoding='utf-8') print(json.dumps({'output':str(output), 'hashMs': [report['buildValidation']['beforeMedianMs'],report['buildValidation']['afterMedianMs']], 'databaseMs':[report['databasePreparation']['beforeMedianMs'],report['databasePreparation']['afterMedianMs']], 'preparedFiles':[before[0]['preparedFiles'],after[0]['preparedFiles']], 'contactsLoaded':[before[0]['loadedContacts'],after[0]['loadedContacts']]},ensure_ascii=False))