paired-api.mjs 4.0 KB

12345678910111213141516
  1. import fs from 'node:fs';import {createHash,randomBytes} from 'node:crypto';import {parse,operator,base,token} from './server-util.mjs';import {patchRound2} from '../../scripts/admin-speed-round2-patch.mjs';
  2. const out='tmp/admin-speed-round2-2026-11-10',root='/Users/mac/.local/state/xiaoshu/cloud-releases/2026-11-10-admin-speed-round2',original=JSON.parse(fs.readFileSync(root+'/original-gateway.json')).code,me=await operator();
  3. const cases=[{page:'payroll-complete',operation:'ops/payroll/overview',month:'2026-11',displayOnly:false},{page:'dashboard',operation:'ops/dashboard/summary'},{page:'coaches',operation:'ops/coaches/list',pageNumber:1,pageSize:20}].filter(c=>!process.env.PERF_PAGES||process.env.PERF_PAGES.split(',').includes(c.page));
  4. function clean(x){if(Array.isArray(x))return x.map(clean);if(x&&typeof x==='object')return Object.fromEntries(Object.entries(x).filter(([k])=>!['refreshedAt','payrollRefreshedAt','lagSeconds','ageMs','cacheHit'].includes(k)).sort(([a],[b])=>a.localeCompare(b)).map(([k,v])=>[k,clean(v)]));return x;}
  5. const hash=x=>createHash('sha256').update(JSON.stringify(clean(x))).digest('hex');const functions=[],results=[];let resetGateway;
  6. try{
  7. if(process.env.PERF_COLD==='1'){
  8. const path='xiaoshu/system/speed2-reset-'+randomBytes(10).toString('hex'),code=`async function handler(request,response){if(request.user?.id!==${JSON.stringify(me.objectId)}||Date.now()>${Date.now()+3600000})return response.status(403).json({success:false});await Psql.none('DELETE FROM "PayrollVerifiedReadCache" WHERE company=$1',['7pIbDBJmKx']);return response.json({success:true});}`;
  9. resetGateway={...await parse('/classes/Function','POST',{name:path,path,code,type:'standalone',enabled:true,params:[],paramList:[],respType:'json'}),path};
  10. }
  11. for(const [variant,source] of Object.entries({before:original,after:patchRound2(original)})){
  12. const path='xiaoshu/system/speed2-api-'+randomBytes(10).toString('hex'),code=source+`\nrealtimeCacheWrite=async()=>{};const actual=handler;handler=async function(request,response){const p=request.body?.params||{};if(request.user?.id!==${JSON.stringify(me.objectId)}||Date.now()>${Date.now()+3600000}||!${JSON.stringify(cases.map(c=>c.operation))}.includes(p.operation))return response.status(403).json({success:false});return actual(request,response);};`;
  13. const row=await parse('/classes/Function','POST',{name:path,path,code,type:'standalone',enabled:true,params:[],paramList:[],respType:'json'});functions.push({...row,path,variant});
  14. }
  15. for(let round=0;round<Number(process.env.PERF_RUNS||10);round++)for(const item of cases){const pair=[];for(const fn of round%2?[...functions].reverse():functions){if(resetGateway&&fn.variant==='after'){const reset=await fetch(base+'/api/functions/'+resetGateway.path,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({token,params:{}})});if(!reset.ok||!(await reset.json()).success)throw Error('Derived-cache reset failed');}const start=performance.now(),params={...item,page:item.pageNumber||1,refresh:true};delete params.pageNumber;const r=await fetch(base+'/api/functions/'+fn.path,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({token,params}),signal:AbortSignal.timeout(60000)}),body=await r.json();if(!r.ok||!body.success)throw Error(item.page+' '+fn.variant+' failed '+String(body.message||body.error||r.status).slice(0,200));const result={round,page:item.page,variant:fn.variant,ms:Math.round(performance.now()-start),hash:hash(body.data),cacheHit:body.data?.sourceStatus?.cacheHit===true};fs.writeFileSync(out+'/'+item.page+'-'+fn.variant+'-latest.json',JSON.stringify(body),{mode:0o600});results.push(result);pair.push(result);}console.log(JSON.stringify({round,page:item.page,equal:pair[0].hash===pair[1].hash,times:pair.map(({variant,ms,cacheHit})=>({variant,ms,cacheHit}))}));fs.writeFileSync(out+'/'+(process.env.PERF_OUTPUT||'api-paired.json'),JSON.stringify(results,null,2),{mode:0o600});}
  16. }finally{for(const fn of [...functions,...(resetGateway?[resetGateway]:[])])await parse('/classes/Function/'+fn.objectId,'DELETE');}