Files
docview/tests/portal/summarize_performance.py
T
2026-09-21 13:41:40 +09:00

78 lines
6.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""Check and summarize all ten completed URL-fix measurements."""
import hashlib
import json
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
OUT = ROOT / 'tests/results/portal/performance-suite'
def sha(path):
with path.open('rb') as stream:
return hashlib.file_digest(stream, 'sha256').hexdigest()
def main():
run = json.loads((OUT / 'report.json').read_text())
assert run['success'] and len(run['commands']) == 5 and all(row['exitCode'] == 0 for row in run['commands'])
assert run['runnerSha256'] == sha(OUT / 'runner-at-start.py')
binaries = {name: sha(ROOT / 'build' / name) for name in ('docview', 'docview-pdf-worker', 'docview-archive-worker')}
assert all(run['binaries'][name] == value for name, value in binaries.items())
rows, operation_count = [], 0
for corpus, formats in [('smoke', ['pdf', 'html', 'zip', 'epub']),
('standard', ['pdf', 'pdf-headings', 'html', 'zip', 'epub']), ('fonts', ['pdf'])]:
for kind in formats:
relative = corpus + '/' + kind + '.json'
value = json.loads((OUT / relative).read_text())
assert value['success'] and value['originalUnchanged'] and all(value['measurementChecks'].values())
assert value['executableSha256'] == binaries and value['runnerSha256'] == sha(ROOT / 'tests/benchmark.py')
assert len(value['openFrameMs']) == 30 and value['coldProcessOpen']['requested'] == 30 and value['coldProcessOpen']['failed'] == 0
assert value['summary']['uniform_open_close_cycles'] == 29 and value['summary']['all_closes_empty']
display = value['display']
assert display['screenLogicalWidth'] == 1100 and display['screenLogicalHeight'] == 760 and display['windowDevicePixelRatio'] == 1
cold = list((OUT / corpus / 'cold' / kind).glob('[0-9][0-9][0-9].json'))
assert len(cold) == 30
for path in cold:
sample = json.loads(path.read_text())
assert sample['success'] and sample['documentSha256'] == value['documentSha256']
assert sample['display'] == display and sample['maximumMemoryPressureLevel'] == 0
operation_count += len(value['operations'])
summary = value['summary']
exceeded = [name for name, observation in value['proposedBudgetObservations'].items()
if observation is False or isinstance(observation, dict) and observation.get('observed_met') is False]
rows.append({'corpus': corpus, 'format': kind, 'record': relative, 'recordSha256': sha(OUT / relative),
'coldOpenP95Ms': summary['fresh_process_fresh_app_cache_open']['p95_ms'],
'responseP95Ms': summary['response']['p95_ms'], 'finalQualityP95Ms': summary['final_quality']['p95_ms'],
'scrollP95Ms': summary['continuous_scroll_frames']['p95_ms'],
'processGroupPeakMiB': value['processGroupRssPeakBytes'] / 1024**2,
'budgetObservationsExceeded': exceeded, 'navigationCoverage': value['navigationCoverage']})
interaction = json.loads((OUT / 'interaction/record.json').read_text())
stress = json.loads((OUT / 'stress/report.json').read_text())
measured = {row['path']: row['sha256'] for row in interaction['binaries']}
assert interaction['success'] and measured['test_app'] == sha(ROOT / 'build/test_app')
assert all(measured[name] == digest for name, digest in binaries.items())
assert stress['success'] and stress['originalUnchanged'] and stress['sourceRemoved']
assert stress['executableSha256'] == binaries and stress['executablesUnchanged']
result = {'success': True, 'buildSha256': binaries, 'cases': rows, 'coldOpens': 300, 'sameProcessOpens': 300,
'summaryGeneratorSha256': sha(Path(__file__)),
'navigationOperations': operation_count, 'scope': run['scope'],
'interactionRecordSha256': sha(OUT / 'interaction/record.json'), 'stressRecordSha256': sha(OUT / 'stress/report.json'),
'proposedBudgetExceedances': sum(len(row['budgetObservationsExceeded']) for row in rows)}
(OUT / 'summary.json').write_text(json.dumps(result, ensure_ascii=False, indent=2) + '\n')
lines = ['# URL比較修正後の性能測定', '',
'10条件で新規プロセス・空のXDGアプリキャッシュ30回、同一プロセス30回、適用される移動系列125操作、連続scroll240入力を実施した。原本不変、全closeの状態解放、メモリ圧迫なしを確認した。', '',
'ArchのXvfb 1100×760、DPR1、software描画。OSファイルキャッシュは保持。起動後のopenを起点とし、プロセス起動時間を含めない。Qt提示通知の測定で、Webの対象画素や物理scanoutの直接測定ではない。基準機・対象OSの性能受入とは区別する。', '',
'| 資料 | 形式 | 冷open p95 ms | 応答 p95 ms | 最終品質 p95 ms | scroll p95 ms | 群RSS最大 MiB |',
'|---|---|---:|---:|---:|---:|---:|']
for row in rows:
lines.append(f"| {row['corpus']} | [{row['format']}]({row['record']}) | {row['coldOpenP95Ms']:.2f} | {row['responseP95Ms']:.2f} | {row['finalQualityP95Ms']:.2f} | {row['scrollP95Ms']:.2f} | {row['processGroupPeakMiB']:.2f} |")
lines += ['', f"新規open 300回・同一プロセスopen 300回・移動系列{operation_count}操作。暫定予算の参考超過は{result['proposedBudgetExceedances']}件。元の予算値は変更していない。未測定の系列は各JSONのnavigationCoverageで区別する。", '',
'[実行コマンド](report.json)、[機械集計](summary.json)、[操作負荷](interaction/record.json)、[約1GB・5,000ページPDF](stress/report.json)を保存した。重いPDFは3open・100操作の追加負荷であり、30回の性能受入標本ではない。', '']
(OUT / 'README.md').write_text('\n'.join(lines))
print(json.dumps({key: result[key] for key in ('success', 'coldOpens', 'sameProcessOpens', 'navigationOperations', 'proposedBudgetExceedances')}))
if __name__ == '__main__':
main()