113 lines
6.1 KiB
Python
113 lines
6.1 KiB
Python
"""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))
|