| 12345678910111213141516171819202122232425262728293031 |
- import {startBrowser,browserCase} from './browser-case.mjs';
- import {readFileSync,writeFileSync} from 'node:fs';
- import {createHash,randomBytes} from 'node:crypto';
- import {candidateCode,withTemporaryIncomeView} from './candidate-code.mjs';
- const base='https://server.xiaoshu.pro/parse',app=process.env.XIAOSHU_PARSE_APP_ID,token=process.env.XIAOSHU_SYNC_OPERATOR_TOKEN,master=process.env.XIAOSHU_MASTER_KEY;
- if(!app||!token||!master)throw Error('Missing authorized configuration');
- const headers={'Content-Type':'application/json','X-Parse-Application-Id':app,'X-Parse-Master-Key':master},out='tmp/admin-data-2026-10-15';
- async function parse(path,method='GET',body){const r=await fetch(base+path,{method,headers,body:body?JSON.stringify(body):undefined,signal:AbortSignal.timeout(20000)}),d=await r.json();if(!r.ok||d.error)throw Error('Setup HTTP '+r.status);return d;}
- const me=await(await fetch(base+'/users/me',{headers:{'X-Parse-Application-Id':app,'X-Parse-Session-Token':token}})).json();if(!me.objectId)throw Error('Invalid operator');
- const operations=process.argv.slice(2).length?process.argv.slice(2):['ops/users/list','ops/stores/list','ops/schedule/calendar','ops/dashboard/summary','ops/payroll/overview'];
- const saved=JSON.parse(readFileSync(out+'/online-before.json','utf8')),expiry=Date.now()+3600000,paths={},created=[];
- // Clock metadata and the dashboard-only settlement-status metadata are omitted.
- // Dashboard estimates explicitly cannot authorize settlement; money, counts and all other fields stay exact. Row ordering, amounts, permissions and statuses remain exact.
- function clean(x,path=[]){if(Array.isArray(x))return x.map((v,i)=>clean(v,[...path,String(i)]));if(x&&typeof x==='object')return Object.fromEntries(Object.keys(x).sort().filter(k=>!['refreshedAt','lagSeconds'].includes(k)&&!(path.join('.')==='payroll.sourceStatus'&&['sync','complete','stale'].includes(k))).map(k=>[k,clean(x[k],[...path,k])]));return x;}
- const hash=x=>createHash('sha256').update(JSON.stringify(clean(x))).digest('hex');
- const measurements=[],browser=process.env.PERF_BROWSER?await startBrowser():null;
- try{
- for(const variant of ['A','B']){
- const body=variant==='A'?withTemporaryIncomeView(saved.code,!process.env.PERF_CURRENT_VIEW):candidateCode(saved.code,{temporaryView:true});
- const guard=`\nconst __original=handler;handler=async function(request,response){if(request.user?.id!==${JSON.stringify(me.objectId)}||Date.now()>${expiry}||!${JSON.stringify(operations)}.includes(request.body?.params?.operation))return response.status(403).json({success:false});return __original(request,response);};`;
- const path='xiaoshu/system/perf-pair-'+randomBytes(10).toString('hex');const fn=await parse('/classes/Function','POST',{name:path,path,code:body+guard,type:'standalone',enabled:true,params:[],paramList:[],respType:'json'});created.push(fn.objectId);paths[variant]=path;
- }
- for(const operation of operations){for(let pair=0;pair<Number(process.env.PERF_ROUNDS||10);pair++){const results=[];
- for(const variant of ['A','B']){if(browser){const entry=await browserCase(browser,{variant,operation,path:paths[variant],token,me,pair,hash});entry.hash=hash(entry.requests.map(r=>r.hash));measurements.push(entry);results.push(entry);continue;}const params={operation,page:1,pageSize:operation==='ops/vocabulary/list'?30:20,search:'',month:'2026-10',dateFrom:'2026-10-15',dateTo:'2026-10-21',source:'database',refresh:true,filters:operation==='ops/vocabulary/list'?{nodeId:0,status:'',missingAudio:false}:{status:'all'}},start=performance.now();
- const r=await fetch(base.replace('/parse','/api/functions/')+paths[variant],{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({token,params}),signal:AbortSignal.timeout(90000)});const ttfb=performance.now()-start,raw=await r.text(),data=JSON.parse(raw),elapsed=performance.now()-start;if(!r.ok||data.success===false)throw Error(operation+' '+variant+' failed: '+data.message);
- if(pair===0)writeFileSync(out+'/pair-'+operation.replaceAll('/','-')+'-'+variant+'.json',raw,{mode:0o600});
- const entry={operation,pair,variant,ms:Math.round(elapsed),ttfbMs:Math.round(ttfb),bytes:Buffer.byteLength(raw),hash:hash(data.data)};measurements.push(entry);results.push(entry);
- }
- const equal=results[0].hash===results[1].hash;console.log(JSON.stringify({operation,pair,A:results[0].ms,B:results[1].ms,equal}));writeFileSync(out+'/'+(process.env.PERF_OUTPUT||(browser?'paired-browser.json':'paired-api.json')),JSON.stringify(measurements,null,2),{mode:0o600});if(!equal)throw Error('Golden output changed: '+operation);
- }}
- }finally{if(browser)await browser.close();for(const id of created)await parse('/classes/Function/'+id,'DELETE');}
|