tricu

An interpreted language for exploring Tree Calculus
Log | Files | Refs | README | LICENSE

browser-memory.mjs (2755B)


      1 // Linux process-tree RSS sampling. Shared pages are double-counted; not PSS,
      2 // private memory or a hard peak. No forced GC and no evaluator instrumentation.
      3 import {readdir,readFile} from 'node:fs/promises';
      4 export function sampleBrowser(root,marks=[]){
      5  const samples=[];let active=true,task;
      6  async function sample(){
      7   const rows=[];
      8   const fields=text=>Object.fromEntries(text.split('\n').filter(x=>x.includes(':')).map(x=>{const at=x.indexOf(':');return [x.slice(0,at),x.slice(at+1).trim()];}));
      9   const own=fields(await readFile('/proc/self/status','utf8'));
     10   for(const pid of (await readdir('/proc')).filter(x=>/^\d+$/.test(x))){
     11    try{const f=fields(await readFile('/proc/'+pid+'/status','utf8'));rows.push({pid:Number(f.Pid),parent:Number(f.PPid),local:Number(f.NSpid?.split(/\s+/).at(-1)??f.Pid),rss:Number(f.VmRSS?.split(/\s+/)[0]??0)*1024});}catch{}
     12   }
     13   // /proc may be mounted from an outer PID namespace. Locate our direct child
     14   // using both the host parent PID and the child's namespace-local PID.
     15   const hostRoot=rows.find(r=>r.parent===Number(own.Pid)&&r.local===root)?.pid;
     16   const family=new Set(hostRoot===undefined?[]:[hostRoot]);let changed=true;
     17   while(changed){changed=false;for(const r of rows)if(family.has(r.parent)&&!family.has(r.pid)){family.add(r.pid);changed=true;}}
     18   const found=rows.filter(r=>family.has(r.pid));const current=marks.at(-1);
     19   let pssBytes=null,privateBytes=null;
     20   if(process.env.BENCHMARK_PSS&&current&&(current.name.startsWith('live-')||['baseline','final-idle'].includes(current.name))){
     21    let pss=0,priv=0,complete=found.length>0;
     22    for(const r of found){try{const f=fields(await readFile('/proc/'+r.pid+'/smaps_rollup','utf8'));pss+=Number(f.Pss.split(/\s+/)[0])*1024;priv+=(Number(f.Private_Clean.split(/\s+/)[0])+Number(f.Private_Dirty.split(/\s+/)[0]))*1024;}catch{complete=false;}}
     23    if(complete){pssBytes=pss;privateBytes=priv;}
     24   }
     25   samples.push({at:performance.now(),phase:current?.name,rssBytes:found.length?found.reduce((s,r)=>s+r.rss,0):null,processes:found.length,pssBytes,privateBytes});
     26  }
     27  task=(async()=>{while(active){await sample();await new Promise(r=>setTimeout(r,100));}})();
     28  return async marks=>{active=false;await task;if(!samples.some(x=>x.rssBytes>0))throw Error('Browser RSS unavailable');return {method:'Linux /proc descendant RSS sum, ~100ms sampling (shared pages double-counted). Optional PSS/private pages sampled only during explicit idle/live memory holds, not timing samples.',samples,phases:marks.map((m,i)=>{const values=samples.filter(x=>x.at>=m.at&&x.at<(marks[i+1]?.at??Infinity)).map(x=>x.rssBytes).filter(x=>x!==null);return {...m,maxRssBytes:values.length?Math.max(...values):null,minRssBytes:values.length?Math.min(...values):null};})};};
     29 }