import json, bisect, collections
from pathlib import Path
paths={'bs1':'artifacts/bs1-audit-search-20260913/memory/placement-533486.json','bs2':'artifacts/bs2-search-20260912/memory/placement-485059.json'}
def union(xs):
 end=-1;total=0
 for a,b in sorted(xs):
  total+=max(0,b-max(a,end));end=max(end,b)
 return total
out={}
for name,path in paths.items():
 x=json.load(open(path)); capacity=x['end']-x['start']; rows=[]
 for tile in x['tiles']:
  aa=tile['allocations']; coords=sorted({v for a in aa for v in (a['start'],a['end'])}); n=len(coords)-1
  cover=[0]*(4*n+4);length=[0]*(4*n+4)
  def update(i,l,r,ql,qr,d):
   if ql<=l and r<=qr: cover[i]+=d
   else:
    m=(l+r)//2
    if ql<m:update(2*i,l,m,ql,qr,d)
    if qr>m:update(2*i+1,m,r,ql,qr,d)
   length[i]=coords[r]-coords[l] if cover[i] else (0 if r-l==1 else length[2*i]+length[2*i+1])
  events=collections.defaultdict(list)
  for a in aa:
   l=bisect.bisect_left(coords,a['start']);r=bisect.bisect_left(coords,a['end'])
   if l==r:continue
   events[a['first']].append((l,r,1))
   if a['last']<4294967295: events[a['last']+1].append((l,r,-1))
  peak=0;step=0
  for t,ev in sorted(events.items()):
   for l,r,d in ev:update(1,0,n,l,r,d)
   if length[1]>peak:peak=length[1];step=t
  live=[a for a in aa if a['first']<=step<=a['last']]
  types={k:union([(a['start'],a['end']) for a in live if a['kind']==k]) for k in {a['kind'] for a in live}}
  rows.append(dict(tile=tile['logical'],peak=peak,step=step,free=capacity-peak,never_used=capacity-union([(a['start'],a['end']) for a in aa]),types=types))
 rows.sort(key=lambda r:r['free'])
 out[name]={'path':path,'capacity':capacity,'minimum':rows[0],'median_free':sorted(r['free'] for r in rows)[len(rows)//2],'average_free':sum(r['free'] for r in rows)/len(rows),'top':rows[:8],'rows':rows}
 print(name,{k:v for k,v in out[name].items() if k not in ('rows','top')},flush=True)
Path('artifacts/performance-headroom-20260913/memory-summary.json').write_text(json.dumps(out,indent=2))
