import {fork} from 'node:child_process';import{once}from'node:events';import{performance,monitorEventLoopDelay}from'node:perf_hooks';import{writeFile,readFile,mkdir}from'node:fs/promises';import{createHash}from'node:crypto';import{setTimeout as delay}from'node:timers/promises';import{availableParallelism,platform,arch}from'node:os';import net from'node:net';
import{makePool}from'../src/pool.mjs';
const results=[];const rounds=50_000_000;
const ms=()=>performance.now(); const hist=h=>({mean:h.mean,p50:h.p50,p99:h.p99,min:h.min,max:h.max});
const checkPort=port=>new Promise((resolve,reject)=>{const s=net.createServer();s.once('error',reject);s.listen(port,'127.0.0.1',()=>s.close(resolve));});
await checkPort(8310);await checkPort(8311);
const child=fork(new URL('../src/server.mjs',import.meta.url),[],{env:{...process.env,PORT:'8310'},silent:true});let stderr='';child.stderr.on('data',b=>stderr+=b);
try{
 await new Promise((resolve,reject)=>{const timeout=setTimeout(()=>reject(new Error('API start timeout')),15000);child.stdout.on('data',b=>{if(b.toString().includes('"ready":true')){clearTimeout(timeout);resolve();}});child.once('exit',()=>{clearTimeout(timeout);reject(new Error('API exited '+stderr));});});
 const rows=[];
 for(const mode of ['main','pool']){
  const start=ms(),pings=[];const task=fetch(`http://127.0.0.1:8310/compute?mode=${mode}&rounds=${rounds}`).then(r=>r.json());
  let done=false;task.finally(()=>done=true);
  while(!done){await delay(8);const t=ms();const response=await fetch('http://127.0.0.1:8310/health');const payload=await response.json();pings.push({atMs:t-start,latencyMs:ms()-t,status:response.status,active:payload.active});}
  rows.push({mode,elapsedMs:ms()-start,result:await task,pings});
 }
 results.push({id:'responsive',title:'Основной поток и HTTP',runs:rows});
 for(const threads of [1,2]){
  const pool=makePool({threads,maxQueue:12});try{await Promise.all(Array.from({length:threads},()=>pool.run({rounds:10})));const start=ms(),events=[];
   const jobs=Array.from({length:6},(_,i)=>{const submitted=ms()-start;return pool.run({rounds:20_000_000,seed:i+1}).then(value=>{events.push({job:i+1,submittedMs:submitted,finishedMs:ms()-start,...value});});});await Promise.all(jobs);
   results.push({id:threads===1?'pool-one':'pool-two',title:`Пул ${threads}`,threads,elapsedMs:ms()-start,events,runTime:hist(pool.histogram.runTime),waitTime:hist(pool.histogram.waitTime)});
  }finally{await pool.destroy();}
 }
 {
  const pool=makePool({threads:1,maxQueue:1});try{await pool.run({rounds:10});const start=ms();const outcomes=await Promise.all(Array.from({length:6},(_,i)=>pool.run({rounds:rounds,seed:i+1}).then(value=>({job:i+1,status:'fulfilled',finishedMs:ms()-start,value}),e=>({job:i+1,status:'rejected',finishedMs:ms()-start,error:e.message}))));results.push({id:'pressure',title:'Ограниченная очередь',elapsedMs:ms()-start,maxQueue:1,threads:1,outcomes});}finally{await pool.destroy();}
 }
 {
  const pool=makePool();try{const start=ms();const compute=await pool.run({rounds:1000000},{name:'compute'}),summary=await pool.run({values:[120,80,30,45]},{name:'summarize'});results.push({id:'named',title:'Именованные задачи',elapsedMs:ms()-start,compute,summary});}finally{await pool.destroy();}
 }
 {
  const pool=makePool({threads:1});try{await pool.run({rounds:10});const runs=[];for(const transfer of [false,true]){const buffer=new ArrayBuffer(8*1024*1024);new Uint8Array(buffer).fill(3);const start=ms();const promise=pool.run({buffer},{name:'sumBuffer',...(transfer?{transferList:[buffer]}:{})});const result=await promise;runs.push({transfer,elapsedMs:ms()-start,senderBytesAfter:buffer.byteLength,result});}results.push({id:'transfer',title:'Копирование и передача',runs});}finally{await pool.destroy();}
 }
 {
  const pool=makePool({threads:1});try{await pool.run({rounds:10});const start=ms();const error=await pool.run({}, {name:'fail'}).then(()=>null,e=>e.message);const controller=new AbortController();const task=pool.run({rounds:200_000_000},{signal:controller.signal}).then(()=>({state:'completed'}),e=>({state:'aborted',name:e.name,message:e.message,atMs:ms()-start}));setTimeout(()=>controller.abort(),15);const aborted=await task;const recovery=await pool.run({rounds:1000});results.push({id:'cancel',title:'Ошибка, отмена и восстановление',elapsedMs:ms()-start,error,aborted,recovery});}finally{await pool.destroy();}
 }
 {
  const pool=makePool({threads:2,maxQueue:6});const start=ms();let completed=0;const events=[];const jobs=Array.from({length:6},(_,i)=>pool.run({rounds:10000000,seed:i}).then(value=>{completed++;events.push({job:i+1,completed,finishedMs:ms()-start,threadId:value.threadId});}));await Promise.all(jobs);await pool.close();const afterClose={workers:pool.threads.length};results.push({id:'service',title:'Партия и корректная остановка',elapsedMs:ms()-start,completed,events,afterClose});
 }
 const sources={};for(const p of ['src/worker.mjs','src/pool.mjs','src/server.mjs','tools/capture.mjs'])sources[p]=createHash('sha256').update(await readFile(p)).digest('hex');
 const recording={kind:'recorded-real-node-piscina-run',recordedAt:new Date().toISOString(),node:process.version,piscina:JSON.parse(await readFile('node_modules/piscina/package.json')).version,platform:platform(),arch:arch(),availableParallelism:availableParallelism(),command:'npm run capture',note:'Measured locally; workload is deterministic CPU arithmetic, not a production benchmark. Results depend on hardware, quotas and load. Warmup is explicit; pool timing histograms include warmup.',sources,results};
 await mkdir('artifacts',{recursive:true});await writeFile('artifacts/recording.json',JSON.stringify(recording,null,2)+'\n');console.log(JSON.stringify({status:'PASS',runs:results.length,recording:'artifacts/recording.json'}));
}finally{
 if(child.connected)child.send('stop');await Promise.race([once(child,'exit'),delay(10000).then(()=>{if(child.exitCode===null)child.kill();})]);
}

