35 lines
7.5 KiB
PHP
35 lines
7.5 KiB
PHP
<?php
|
|
use Illuminate\Support\Facades\DB;
|
|
use App\Modules\Purchasing\SupplierPayables;
|
|
use App\Shared\Context\CompanyContext;
|
|
config(['supplier_payable_history.synthetic_enabled'=>true]);$f=json_decode(file_get_contents($s['root'].'/fixture.json'),true,64,JSON_THROW_ON_ERROR);$u=App\Models\User::findOrFail($f['actors']['read']);auth()->setUser($u);app(CompanyContext::class)->selectFor($u->id,$f['scope']['tenant_id'],$f['scope']['company_id'],$f['scope']['unit_id']);
|
|
$mode=substr($phase,strlen('payable-report-profile-'));if(!in_array($mode,['before','after','plan','analyze','analyzed','final','cases','preservation','command-probe'],true))throw new LogicException('Unknown profile phase');$out=$s['root'].'/profile-'.$mode.'.json';if(file_exists($out))throw new LogicException('Completed measurement preserved');
|
|
if($mode==='command-probe'){
|
|
DB::beginTransaction();try{$actor=App\Models\User::findOrFail($f['actors']['prepare']);auth()->setUser($actor);app(CompanyContext::class)->selectFor($actor->id,$f['scope']['tenant_id'],$f['scope']['company_id'],$f['scope']['unit_id']);$engine=app(SupplierPayables::class)->engine($actor);$key=(string)Illuminate\Support\Str::uuid();$first=$engine->command('prepare_history',$f['source_id'],'Rollback-only live historical command compatibility after read optimisation.',$key);$second=$engine->command('prepare_history',$f['source_id'],'Rollback-only live historical command compatibility after read optimisation.',$key);if($first['state']!=='recognised'||!$second['replayed']||DB::table('pa_obligations')->count()!==10000)throw new LogicException('Live command compatibility changed');file_put_contents($out,json_encode(['passed'=>true,'live_history_command'=>true,'existing_recognition_preserved'=>true,'same_key_replayed'=>true,'rollback_only'=>true,'obligations'=>10000],JSON_PRETTY_PRINT));echo "Live history command and replay still recover the original recognition; probe rolled back.\n";}finally{while(DB::transactionLevel())DB::rollBack();}exit;
|
|
}
|
|
if($mode==='cases'){require __DIR__.'/payable-report-capture-cases.php';exit;}
|
|
if($mode==='preservation'){
|
|
$prior=json_decode(file_get_contents('C:/laragon/data/sgf-tempo/run/modular-regression-2576427d1939/private/payable-history-03e16d9fb37d/page-post.json'),true);$hash=hash_init('sha256');foreach(['pa_imported_sources','pa_source_histories','pa_sources','pa_proposals','pa_obligations','pa_history_imports','pa_imported_payments','pa_events','pa_commands','pa_access'] as $table){hash_update($hash,$table."\n");foreach(DB::table($table)->where($f['scope'])->orderBy('id')->cursor() as $r)hash_update($hash,Tempo\Finance\Payables\Payables::canonical((array)$r)."\n");}$actual=hash_final($hash);if($actual!==$prior['scoped_business_fingerprint'])throw new LogicException('Original synthetic financial fingerprint changed');file_put_contents($out,json_encode(['passed'=>true,'clone_matches_valid_original_business_fingerprint'=>true,'fingerprint'=>$actual,'data_writes'=>0,'new_indexes'=>0],JSON_PRETTY_PRINT));echo "Synthetic clone still matches the original valid business fingerprint.\n";exit;
|
|
}
|
|
if($mode==='analyze'){if(($s['profile_clone_of']??null)!=='03e16d9fb37d')throw new LogicException('Only newly cloned synthetic database may receive statistics maintenance');DB::statement('ANALYZE');file_put_contents($out,json_encode(['passed'=>true,'only_new_synthetic_clone'=>true,'new_indexes'=>false,'source_data_modified'=>false],JSON_PRETTY_PRINT));echo "Statistics updated only in new synthetic clone; no indexes or destination changes.\n";exit;}
|
|
if($mode==='plan'){
|
|
$sql="SELECT r.* FROM pa_imported_sources r WHERE r.tenant_id=? AND r.company_id=? AND r.unit_id=? AND EXISTS(SELECT 1 FROM pa_sources s JOIN pa_obligations o ON o.source_id=s.id WHERE s.local_source_id=r.id AND s.tenant_id=r.tenant_id AND s.company_id=r.company_id AND s.unit_id=r.unit_id AND o.tenant_id=r.tenant_id AND o.company_id=r.company_id AND o.unit_id=r.unit_id) ORDER BY r.id DESC LIMIT 100";
|
|
DB::beginTransaction();try{DB::statement('SET TRANSACTION READ ONLY');$plan=DB::select('EXPLAIN (ANALYZE,BUFFERS,FORMAT JSON) '.$sql,array_values($f['scope']));file_put_contents($out,json_encode(['passed'=>true,'plan'=>$plan,'read_only'=>true],JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));}finally{DB::rollBack();}exit;
|
|
}
|
|
$source=$mode==='before'?file_get_contents($s['root'].'/baseline.php'):str_replace('final class SupplierPayables','final class ProfileBaselineSupplierPayables',file_get_contents(__DIR__.'/../app/Modules/Purchasing/SupplierPayables.php'));
|
|
$source=str_replace("\r\n","\n",$source);$source=str_replace('final class ProfileBaselineSupplierPayables'."\n".'{','final class ProfileBaselineSupplierPayables'."\n".'{ public static array $profile=[];',$source);
|
|
// Instrument an archived private class only; never expose a profiling switch in HTTP.
|
|
foreach([
|
|
'selection'=>[' $window = clone $query;',' $documents = [];'],
|
|
'decode'=>[' $documents = [];',' $histories = $historical'],
|
|
'history'=>[' $histories = $historical',' $engine = new Payables'],
|
|
'positions'=>[' $engine = new Payables',' foreach ($data[\'documents\'] as $doc)'],
|
|
'hash_totals'=>[' foreach ($data[\'documents\'] as $doc)',' $windows++;']
|
|
] as $name=>[$start,$end]){
|
|
$a=strpos($source,$start,strpos($source,'public function report('));$b=strpos($source,$end,$a+strlen($start));if($a===false||$b===false)throw new LogicException('Profiler anchor missing '.$name);
|
|
$source=substr($source,0,$b).'self::$profile['.var_export($name,true).']=(self::$profile['.var_export($name,true).']??0)+(hrtime(true)-$profileTimer)/1e6;'."\n".substr($source,$b);$source=substr($source,0,$a).'$profileTimer=hrtime(true);'."\n".substr($source,$a);
|
|
}
|
|
$classFile=$s['root'].'/profiled-'.$mode.'.php';file_put_contents($classFile,$source);require $classFile;$queries=[];$locks=0;DB::listen(function($event)use(&$queries,&$locks){$key=$event->sql;$queries[$key]??=['queries'=>0,'sql_ms'=>0,'sql'=>$key];$queries[$key]['queries']++;$queries[$key]['sql_ms']+=$event->time;if(str_contains(strtolower($key),'for update'))$locks++;});gc_collect_cycles();memory_reset_peak_usage();$at=hrtime(true);$r=app(App\Modules\Purchasing\ProfileBaselineSupplierPayables::class)->report($u);$ms=(hrtime(true)-$at)/1e6;$peak=memory_get_peak_usage(true);$groups=array_values($queries);usort($groups,fn($a,$b)=>$b['sql_ms']<=>$a['sql_ms']);if($r['totals']['count']!==10000||$r['totals']['balance_minor']!==30000000||$locks)throw new LogicException('Profile universe/balance/locks differs');
|
|
if(in_array($mode,['after','final'],true)){$before=json_decode(file_get_contents($s['root'].'/profile-before.json'),true);foreach(['totals','known_positions','positions_sha256','windows'] as $key)if($before['report'][$key]!==$r[$key])throw new LogicException('Report changed after optimisation');}
|
|
file_put_contents($out,json_encode(['passed'=>true,'mode'=>$mode,'time_ms'=>$ms,'queries'=>array_sum(array_column($groups,'queries')),'sql_ms'=>array_sum(array_column($groups,'sql_ms')),'heap_peak_bytes'=>$peak,'stages_ms'=>App\Modules\Purchasing\ProfileBaselineSupplierPayables::$profile,'query_groups'=>$groups,'report'=>$r,'writer_locks'=>$locks,'private_synthetic_clone_only'=>true,'not_deployment_capacity'=>true],JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));echo json_encode(['mode'=>$mode,'time_ms'=>$ms,'queries'=>array_sum(array_column($groups,'queries')),'stages_ms'=>App\Modules\Purchasing\ProfileBaselineSupplierPayables::$profile])."\n";
|