trace-viz 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +42 -0
- package/LICENSE +21 -0
- package/README.md +385 -0
- package/dist/adapters/index.d.ts +32 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +83 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/agent.d.ts +66 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +329 -0
- package/dist/agent.js.map +1 -0
- package/dist/atlas/atlas.d.ts +203 -0
- package/dist/atlas/atlas.d.ts.map +1 -0
- package/dist/atlas/atlas.js +1466 -0
- package/dist/atlas/atlas.js.map +1 -0
- package/dist/atlas/index.d.ts +7 -0
- package/dist/atlas/index.d.ts.map +1 -0
- package/dist/atlas/index.js +4 -0
- package/dist/atlas/index.js.map +1 -0
- package/dist/atlas/layout.d.ts +44 -0
- package/dist/atlas/layout.d.ts.map +1 -0
- package/dist/atlas/layout.js +103 -0
- package/dist/atlas/layout.js.map +1 -0
- package/dist/atlas/model.d.ts +164 -0
- package/dist/atlas/model.d.ts.map +1 -0
- package/dist/atlas/model.js +489 -0
- package/dist/atlas/model.js.map +1 -0
- package/dist/channels.d.ts +105 -0
- package/dist/channels.d.ts.map +1 -0
- package/dist/channels.js +411 -0
- package/dist/channels.js.map +1 -0
- package/dist/components.d.ts +76 -0
- package/dist/components.d.ts.map +1 -0
- package/dist/components.js +432 -0
- package/dist/components.js.map +1 -0
- package/dist/estate.d.ts +25 -0
- package/dist/estate.d.ts.map +1 -0
- package/dist/estate.js +285 -0
- package/dist/estate.js.map +1 -0
- package/dist/failure.d.ts +36 -0
- package/dist/failure.d.ts.map +1 -0
- package/dist/failure.js +82 -0
- package/dist/failure.js.map +1 -0
- package/dist/geometry.d.ts +24 -0
- package/dist/geometry.d.ts.map +1 -0
- package/dist/geometry.js +92 -0
- package/dist/geometry.js.map +1 -0
- package/dist/index.d.ts +51 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/dist/layout.d.ts +66 -0
- package/dist/layout.d.ts.map +1 -0
- package/dist/layout.js +152 -0
- package/dist/layout.js.map +1 -0
- package/dist/layout3d.d.ts +98 -0
- package/dist/layout3d.d.ts.map +1 -0
- package/dist/layout3d.js +350 -0
- package/dist/layout3d.js.map +1 -0
- package/dist/mesh.d.ts +23 -0
- package/dist/mesh.d.ts.map +1 -0
- package/dist/mesh.js +263 -0
- package/dist/mesh.js.map +1 -0
- package/dist/model.d.ts +94 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/model.js +84 -0
- package/dist/model.js.map +1 -0
- package/dist/particles.d.ts +80 -0
- package/dist/particles.d.ts.map +1 -0
- package/dist/particles.js +118 -0
- package/dist/particles.js.map +1 -0
- package/dist/project.d.ts +113 -0
- package/dist/project.d.ts.map +1 -0
- package/dist/project.js +288 -0
- package/dist/project.js.map +1 -0
- package/dist/react/index.d.ts +22 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +69 -0
- package/dist/react/index.js.map +1 -0
- package/dist/renderer.d.ts +252 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/renderer.js +1710 -0
- package/dist/renderer.js.map +1 -0
- package/dist/scales.d.ts +99 -0
- package/dist/scales.d.ts.map +1 -0
- package/dist/scales.js +126 -0
- package/dist/scales.js.map +1 -0
- package/dist/scene.d.ts +53 -0
- package/dist/scene.d.ts.map +1 -0
- package/dist/scene.js +44 -0
- package/dist/scene.js.map +1 -0
- package/dist/schema.d.ts +24 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +131 -0
- package/dist/schema.js.map +1 -0
- package/dist/seed.d.ts +33 -0
- package/dist/seed.d.ts.map +1 -0
- package/dist/seed.js +173 -0
- package/dist/seed.js.map +1 -0
- package/dist/theme.d.ts +35 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +83 -0
- package/dist/theme.js.map +1 -0
- package/dist/trace-viz.esm.js +15 -0
- package/dist/trace-viz.min.js +15 -0
- package/dist/types.d.ts +92 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/package.json +105 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";var tracelight=(()=>{var _t=Object.defineProperty;var Vn=Object.getOwnPropertyDescriptor;var zn=Object.getOwnPropertyNames;var Kn=Object.prototype.hasOwnProperty;var Un=(r,e)=>{for(var s in e)_t(r,s,{get:e[s],enumerable:!0})},_n=(r,e,s,t)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of zn(e))!Kn.call(r,n)&&n!==s&&_t(r,n,{get:()=>e[n],enumerable:!(t=Vn(e,n))||t.enumerable});return r};var Xn=r=>_n(_t({},"__esModule",{value:!0}),r);var js={};Un(js,{Atlas:()=>Vt,CATEGORICAL:()=>Ge,CHANNELS:()=>Q,COMPONENTS:()=>ue,ERROR_CEIL:()=>Oe,ERROR_FLOOR:()=>he,EdgeStream:()=>He,GraphValidationError:()=>Se,LAYOUT:()=>rn,LENSES:()=>qn,TIER_LABELS:()=>Et,TraceLight:()=>Nt,aggregateEdges:()=>Gt,authChallengeScenario:()=>$n,baseOf:()=>Re,borderPoint:()=>Tt,buildAtlas:()=>It,buildPrompt:()=>Zt,clamp:()=>V,couplingFor:()=>Qe,darkTheme:()=>rt,defaultEncoding:()=>ne,defaultLayout:()=>nt,defaultTier:()=>Be,defaultWorldLayout:()=>ke,describeTable:()=>mt,distanceToPath:()=>At,edgePath:()=>Pt,edgeWidth:()=>Ie,emissionRate:()=>be,emptyFailure:()=>Ze,emptyState:()=>ht,endpointDims:()=>Ye,extractJson:()=>tn,fieldValue:()=>cn,fitCamera:()=>tt,formatBytes:()=>bt,formatLatency:()=>Pe,formatRps:()=>Z,fromEventSource:()=>In,fromObject:()=>Fn,fromUrl:()=>Dn,generateEstate:()=>Ln,generateMesh:()=>On,generateSeedGraph:()=>Cn,heuristicSpec:()=>dt,isMeasure:()=>ye,layoutAtlas:()=>qt,layoutGraph:()=>Lt,layoutWorld:()=>Ot,lightTheme:()=>it,logNorm:()=>J,mulberry32:()=>ee,neighbourhood:()=>ut,nodeIdFor:()=>ft,particleRadius:()=>De,particleSpeed:()=>we,pickComponent:()=>Rt,pollSource:()=>sn,project:()=>gt,project3:()=>Me,propagate:()=>Bt,propagateFailure:()=>Mt,proposeSpec:()=>Pn,resolveGraph:()=>st,resolveTheme:()=>me,restOf:()=>pt,rpsDomain:()=>ve,safeResolveGraph:()=>Mn,sceneFromGraph:()=>$t,search:()=>Ht,styleScene:()=>xt,themes:()=>kn,tokenAuthScenario:()=>Nn,trail:()=>Wt,validateSpec:()=>en,visibleUnits:()=>jt,worldExtent:()=>qe});function mt(r,e=64){let s=new Map,t=new Set,n=(a,l)=>{if(a)for(let[d,c]of Object.entries(a)){let h=s.get(d);h||s.set(d,h={values:new Set,endpoint:!1,flow:!1}),h.values.add(c),h[l]=!0}};for(let a of r.records){n(a.from,"endpoint"),n(a.to,"endpoint"),n(a.dims,"flow");for(let[l,d]of Object.entries(a.metrics))typeof d=="number"&&t.add(l)}let i=new Set;for(let a of r.nodes??[]){n(a.key,"endpoint"),n(a.dims,"endpoint");for(let l of Object.keys(a.attrs??{}))i.add(l)}let o=[...s.entries()].map(([a,l])=>{let d=[...l.values].sort();return{name:a,values:d.slice(0,e),cardinality:d.length,truncated:d.length>e,endpoint:l.endpoint,flow:l.flow}}).sort((a,l)=>a.cardinality-l.cardinality||a.name.localeCompare(l.name));return t.has("rps")&&t.add("share"),t.has("errorRate")&&i.add("errorRate"),{dimensions:o,measures:[...t].sort(),nodeAttrs:[...i].sort(),records:r.records.length}}function Ye(r,e){return{...r.dims,...r[e]}}function cn(r,e,s){if(s){let t=r[s][e];if(t!==void 0)return t}return r.dims?.[e]??r.from[e]??r.to[e]}var Yn=new Set(["hue","glyph","dash","lane","coupling","density","speed","radius"]);function ft(r,e){return e.map(s=>r[s]??"\u2205").join(" / ")}function hn(r,e){return e?Object.entries(e.match).every(([s,t])=>r[s]===t):!1}function gt(r,e){let s=e.nodeKey.length?e.nodeKey:["service"],t=new Set;for(let[u,g]of Object.entries(e.channels??{}))Yn.has(u)&&(ye(g.field)||t.add(g.field));for(let u of s)t.delete(u);let n=[...t],i=0,o=0,a=new Map,l=new Map;for(let u of r.records){if(!Zn(u,e.where)){o++;continue}i++;let g=Ye(u,"from"),m=Ye(u,"to"),f=hn(g,e.focus)?[...s,...e.focus.expandBy]:s,v=hn(m,e.focus)?[...s,...e.focus.expandBy]:s,R=ft(g,f),E=ft(m,v),b=Math.max(u.metrics.rps,0);if(pn(a,R,g,f,u,{outbound:b}),pn(a,E,m,v,u,{inbound:b}),e.dropSelfEdges&&R===E)continue;let k={};for(let L of n){let N=u.dims?.[L]??u.from[L]??u.to[L];N!==void 0&&(k[L]=N)}let w=[R,"\u2192",E,...n.map(L=>k[L]??"\u2205")].join("|"),A=l.get(w);A||l.set(w,A={id:w,from:R,to:E,dims:k,spans:{},rps:0,latencyWeighted:0,errorWeighted:0,bytesWeighted:0,records:0}),A.rps+=b,A.latencyWeighted+=(u.metrics.latencyMs??25)*b,A.errorWeighted+=(u.metrics.errorRate??0)*b,A.bytesWeighted+=(u.metrics.bytes??1024)*b,A.records++,Xt(A.spans,{...u.dims,...u.from},n,s),Xt(A.spans,{...u.dims,...u.to},n,s)}let d=[...a.values()].map(u=>({id:u.id,label:u.label,...u.sublabel?{sublabel:u.sublabel}:{},kind:u.kind,key:u.key,spans:mn(u.spans),inboundRps:u.inbound,outboundRps:u.outbound,attrs:{},records:u.records}));Jn(d,r.nodes??[]);let c=new Map;for(let u of l.values()){let g=c.get(u.to)??{err:0,rps:0};g.err+=u.errorWeighted,g.rps+=u.rps,c.set(u.to,g)}for(let u of d){let g=c.get(u.id);u.attrs.errorRate=g&&g.rps>0?Fe(g.err/g.rps,5):0}for(let u of d)u.kind==="service"&&(u.inboundRps===0&&u.outboundRps>0?u.kind="client":u.outboundRps===0&&u.inboundRps>0&&(u.kind="datastore"));let h=new Map;for(let u of l.values())h.set(u.from,(h.get(u.from)??0)+u.rps);let p=[...l.values()].map(u=>({id:u.id,from:u.from,to:u.to,kind:es(u.dims),dims:u.dims,spans:mn(u.spans),metrics:{rps:Fe(u.rps,2),latencyMs:u.rps>0?Fe(u.latencyWeighted/u.rps,2):0,errorRate:u.rps>0?Fe(u.errorWeighted/u.rps,5):0,bytes:u.rps>0?Math.round(u.bytesWeighted/u.rps):0,share:(h.get(u.from)??0)>0?Fe(u.rps/h.get(u.from),4):0},records:u.records,...fn(u.dims,n)?{label:fn(u.dims,n)}:{}}));return{scene:{nodes:d,edges:p,meta:{...r.meta,...e.title?{title:e.title}:{}},nodeKey:s},splitBy:n,matched:i,filtered:o}}function Jn(r,e){var n;if(!e.length)return;let s=new Map,t=(i,o)=>i.key[o]??(i.spans[o]?.length===1?i.spans[o][0]:void 0);for(let i of e){let o=Object.entries(i.key);if(o.length)for(let a of r){if(!o.every(([c,h])=>t(a,c)===h)||Object.entries(i.dims??{}).some(([c,h])=>{let p=t(a,c);return p!==void 0&&p!==h}))continue;let d=i.attrs?.instances??1;for(let[c,h]of Object.entries(i.attrs??{}))un.has(c)?a.attrs[c]=(a.attrs[c]??0)+h:a.attrs[c]=(a.attrs[c]??0)+h*d;s.set(a.id,(s.get(a.id)??0)+d),i.component&&(a.component=i.component);for(let[c,h]of Object.entries(i.dims??{}))((n=a.spans)[c]??(n[c]=[])).includes(h)||a.spans[c].push(h)}}for(let i of r){let o=s.get(i.id);if(o)for(let a of Object.keys(i.attrs))un.has(a)||(i.attrs[a]=Fe(i.attrs[a]/o,3))}}var un=new Set(["instances","replicas","pods","shards","connections"]),Qn=new Set(["rps","latencyMs","errorRate","bytes","share"]);function ye(r){return Qn.has(r)}function Zn(r,e){if(!e)return!0;for(let[s,t]of Object.entries(e)){if(!t.length)continue;let n=r.dims?.[s]??r.from[s]??r.to[s];if(n===void 0||!t.includes(n))return!1}return!0}function pn(r,e,s,t,n,i){let o=r.get(e);if(!o){let a=t.map(l=>s[l]??"\u2205");r.set(e,o={id:e,label:a[a.length-1]??e,...a.length>1?{sublabel:a.slice(0,-1).join(" \xB7 ")}:{},kind:s.kind??"service",key:Object.fromEntries(t.map((l,d)=>[l,a[d]])),spans:{},inbound:0,outbound:0,records:0})}o.inbound+=i.inbound??0,o.outbound+=i.outbound??0,o.records++,Xt(o.spans,s,[],t)}function Xt(r,e,s,t){for(let[n,i]of Object.entries(e))s.includes(n)||t.includes(n)||(r[n]??(r[n]=new Set)).add(i)}function mn(r){let e={};for(let[s,t]of Object.entries(r))e[s]=[...t].sort();return e}function fn(r,e){let s=e.map(t=>r[t]).filter(Boolean);return s.length?s.join(" \xB7 "):void 0}function es(r){let e=r.kind??r.edgeKind;return e==="retry"||e==="challenge"||e==="response"||e==="async"||e==="request"?e:"request"}function Fe(r,e){let s=10**e;return Math.round(r*s)/s}var ne={maxEmitPerSecond:26,minEmitPerSecond:.3,maxParticlesPerEdge:120,densityCurve:1.7,maxSpeedPxPerSecond:108,minSpeedPxPerSecond:14,fastLatencyMs:2,slowLatencyMs:800,baseRadiusPx:2.3,minRadiusPx:1.3,maxRadiusPx:5.6,bytesReference:1024};function V(r,e,s){return r<e?e:r>s?s:r}function J(r,e,s){if(!(s>e))return r>0?1:0;let t=Math.log1p(Math.max(0,r)),n=Math.log1p(Math.max(0,e)),i=Math.log1p(Math.max(0,s));return i>n?V((t-n)/(i-n),0,1):1}function be(r,e,s=ne){if(r<=0)return 0;let t=yt(J(r,e.min,e.max),s.densityCurve);return s.minEmitPerSecond+t*(s.maxEmitPerSecond-s.minEmitPerSecond)}function yt(r,e){return Math.pow(V(r,0,1),Math.max(.2,e))}function we(r,e=ne){let s=J(V(r,e.fastLatencyMs,e.slowLatencyMs),e.fastLatencyMs,e.slowLatencyMs);return e.maxSpeedPxPerSecond-s*(e.maxSpeedPxPerSecond-e.minSpeedPxPerSecond)}function De(r,e=ne){let s=e.baseRadiusPx*Math.sqrt(Math.max(r,1)/e.bytesReference);return V(s,e.minRadiusPx,e.maxRadiusPx)}function Ie(r,e,{min:s=.8,max:t=9,curve:n=ne.densityCurve}={}){return r<=0?s*.6:s+yt(J(r,e.min,e.max),n)*(t-s)}function ve(r){let e=r.filter(t=>t>0);if(!e.length)return{min:0,max:1};let s=Math.max(...e);return{min:Math.min(Math.min(...e),s/1e3),max:s}}function Z(r){return r===0?"0":r>=1e6?`${(r/1e6).toFixed(r>=1e7?0:1)}M`:r>=1e3?`${(r/1e3).toFixed(r>=1e4?0:1)}k`:r>=10?r.toFixed(0):r.toFixed(r<1?2:1)}function Pe(r){return r>=1e3?`${(r/1e3).toFixed(r>=1e4?0:1)}s`:r>=10?`${r.toFixed(0)}ms`:`${r.toFixed(1)}ms`}function bt(r){return r>=1024*1024?`${(r/1024/1024).toFixed(1)}MB`:r>=1024?`${(r/1024).toFixed(1)}kB`:`${r.toFixed(0)}B`}var he=.004,Oe=.06,Q={density:{name:"density",target:"edge",accepts:"measure",summary:"particles per second along the edge (log-scaled)"},speed:{name:"speed",target:"edge",accepts:"measure",summary:"how fast particles travel; inverted, so high values crawl"},radius:{name:"radius",target:"edge",accepts:"measure",summary:"particle size (sqrt-scaled)"},hue:{name:"hue",target:"edge",accepts:"dimension",maxCardinality:12,summary:"particle colour, one per category"},glyph:{name:"glyph",target:"edge",accepts:"dimension",maxCardinality:6,summary:"particle shape; readable in greyscale, so it pairs with hue"},dash:{name:"dash",target:"edge",accepts:"dimension",maxCardinality:5,summary:"the edge line pattern under the particles"},lane:{name:"lane",target:"edge",accepts:"dimension",maxCardinality:6,summary:"splits an edge into parallel lanes, one per category"},coupling:{name:"coupling",target:"edge",accepts:"dimension",maxCardinality:3,summary:"failure semantics: a rigid conduit that propagates death, or a breakaway link that stops it"},distance:{name:"distance",target:"edge",accepts:"both",maxCardinality:24,summary:"how close things sit: bind a measure and heavy callers pull together, bind a dimension and same-value things cluster"},nodeFill:{name:"nodeFill",target:"node",accepts:"dimension",maxCardinality:10,summary:"node fill colour, one per category"},halo:{name:"halo",target:"node",accepts:"measure",summary:"a ring around the node sized by a measure - traffic through it, or any numeric fact such as instances. Bound to an error rate it becomes a red glow that scales with how bad things are: 0.5% is a hint, 5% is a siren"},column:{name:"column",target:"node",accepts:"dimension",maxCardinality:12,summary:"forces nodes into columns by category instead of inferring layers"}},Ge=["#3b82f6","#10b981","#a855f7","#06b6d4","#ec4899","#84cc16","#8b5cf6","#14b8a6","#eab308","#f97316"],wt=["dot","ring","square","triangle","bar","diamond"],vt=[null,[7,5],[2,4],[12,4,2,4],[1,5]],ts={dot:"\u25CF",ring:"\u25CD",square:"\u25A0",triangle:"\u25B2",bar:"\u25AC",diamond:"\u25C6"};function Ae(r,e){let s=[...new Set(r)];if(!e?.length)return s.sort();let t=new Map(e.map((n,i)=>[n,i]));return s.sort((n,i)=>{let o=t.get(n),a=t.get(i);return o!==void 0&&a!==void 0?o-a:o!==void 0?-1:a!==void 0?1:n.localeCompare(i)})}function je(r,e){return r.metrics[e]??0}function ce(r,e){return r.dims[e]??(r.spans[e]?.length===1?r.spans[e][0]:void 0)}function Je(r,e){return r.key[e]??(r.spans[e]?.length===1?r.spans[e][0]:void 0)}function xt(r,e,s,t=ne){let n=e.channels??{},i=[],o=[],a=ve(r.edges.map(S=>S.metrics.rps)),l=(S,O,D)=>{let W=Q[S],j=new Map(O.map((z,de)=>[z,de]));if(W.maxCardinality&&O.length>W.maxCardinality){let z=`${O.length} distinct values of "${D.field}" is too many for ${S} (max ${W.maxCardinality}); left at the default`;return o.push(z),i.push({channel:S,field:D.field,kind:"dimension",summary:W.summary,swatches:[],warning:z}),{index:j,ok:!1}}return{index:j,ok:!0}},d=(S,O,D)=>{let W=D.scale??(D.field==="share"||D.field==="errorRate"?"linear":"log"),j;return W==="linear"?j=O.max>O.min?(S-O.min)/(O.max-O.min):1:W==="sqrt"?j=O.max>0?Math.sqrt(Math.max(S,0)/O.max):0:j=J(S,O.min,O.max),D.invert?1-j:j},c=S=>{if(S==="share"||S==="errorRate")return{min:0,max:1};let O=r.edges.map(W=>je(W,S)).filter(W=>W>0);if(!O.length)return{min:0,max:1};let D=Math.max(...O);return{min:Math.min(Math.min(...O),D/1e3),max:D}},h=new Map,p=n.hue,u=p&&!ye(p.field)?Ae(r.edges.map(S=>ce(S,p.field)??"\u2014")):[],g=p?l("hue",u,p):null,m=n.glyph,f=m?Ae(r.edges.map(S=>ce(S,m.field)??"\u2014")):[],v=m?l("glyph",f,m):null,R=n.dash,E=R?Ae(r.edges.map(S=>ce(S,R.field)??"\u2014")):[],b=R?l("dash",E,R):null,k=n.lane,w=k?Ae(r.edges.map(S=>ce(S,k.field)??"\u2014"),k.order):[],A=k?l("lane",w,k):null,L=n.distance,N=!!(L&&ye(L.field)),y=L&&N?c(L.field):{min:0,max:1},x=L&&!N?L.field:null,P=n.coupling,C=P?Ae(r.edges.map(S=>ce(S,P.field)??"\u2014")):[],T=n.density,F=T?c(T.field):a,I=n.speed,H=I?c(I.field):{min:0,max:1},G=n.radius,B=G?c(G.field):{min:0,max:1},X=S=>S?.palette??Ge;for(let S of r.edges){let O;if(T){let q=yt(d(je(S,T.field),F,T),t.densityCurve);O=je(S,T.field)<=0?0:t.minEmitPerSecond+q*(t.maxEmitPerSecond-t.minEmitPerSecond)}else O=be(S.metrics.rps,a,t);let D;if(I){let q=d(je(S,I.field),H,I);D=t.maxSpeedPxPerSecond-q*(t.maxSpeedPxPerSecond-t.minSpeedPxPerSecond)}else D=we(S.metrics.latencyMs,t);let W;if(G){let q=d(je(S,G.field),B,{...G,scale:G.scale??"sqrt"});W=t.minRadiusPx+q*(t.maxRadiusPx-t.minRadiusPx)}else W=De(S.metrics.bytes,t);let j=s.flow[S.kind];if(p&&g?.ok){let q=ce(S,p.field)??"\u2014",Xe=X(p);j=Yt(p,q)??Xe[(g.index.get(q)??0)%Xe.length]}let z="dot";if(m&&v?.ok){let q=ce(S,m.field)??"\u2014";z=Yt(m,q)??wt[(v.index.get(q)??0)%wt.length]}let de=S.kind==="retry"||S.kind==="challenge"?[5,5]:null;if(R&&b?.ok){let q=ce(S,R.field)??"\u2014";de=vt[(b.index.get(q)??0)%vt.length]??null}let Y=0,U=null;if(k&&A?.ok){let q=ce(S,k.field)??"\u2014";Y=A.index.get(q)??0,U=q}let ie="plain";if(P){let q=ce(S,P.field)??"\u2014";ie=Qe(q,P)}let _e=L?.strength??1,Kt=V((L&&N?d(je(S,L.field),y,L):V(S.metrics.share??.3,0,1))*_e,0,2);h.set(S.id,{density:O,speed:D,radius:W,hue:j,glyph:z,dash:de,lane:Y,laneLabel:U,coupling:ie,attraction:Kt})}let le=new Map,K=n.nodeFill,Ue=K?Ae(r.nodes.map(S=>Je(S,K.field)??"\u2014")):[],Ce=K?l("nodeFill",Ue,K):null,_=n.column,Ee=_?Ae(r.nodes.map(S=>Je(S,_.field)??"\u2014"),_.order):[],$e=_?l("column",Ee,_):null,Te=n.halo,Ne=S=>{let O=Te?.field??"inboundRps";return O==="outboundRps"?S.outboundRps:O==="inboundRps"||O==="rps"?S.inboundRps:S.attrs[O]??0},M=r.nodes.map(Ne),$=Math.max(1,...M);for(let S of r.nodes){let O=s.node[S.kind];if(K&&Ce?.ok){let Y=Je(S,K.field)??"\u2014",U=X(K);O=ns(U[(Ce.index.get(Y)??0)%U.length],s.name==="dark"?.72:.84,s)}let D=0,W=0,j=/error|fail|5xx/i.test(Te?.field??"");if(Te){let Y=Ne(S),U;j?U=Y>he?J(Y,he,Oe):0:Te.field==="share"?U=V(Y/Math.max($,1e-4),0,1):U=Y>0?J(Y,Math.max($/1e3,.001),$):0,W=U,D=U>0?3+U*13:0}let z=_?Je(S,_.field)??null:null,de=_&&$e?.ok?$e.index.get(Je(S,_.field)??"\u2014")??null:null;le.set(S.id,{fill:O,halo:D,haloDanger:j,haloIntensity:W,column:de,group:z})}if(p&&g?.ok){let S=X(p);i.push({channel:"hue",field:p.field,kind:"dimension",summary:Q.hue.summary,swatches:u.map((O,D)=>({value:O,color:Yt(p,O)??S[D%S.length]}))})}m&&v?.ok&&i.push({channel:"glyph",field:m.field,kind:"dimension",summary:Q.glyph.summary,swatches:f.map((S,O)=>({value:S,mark:ts[wt[O%wt.length]]}))}),R&&b?.ok&&i.push({channel:"dash",field:R.field,kind:"dimension",summary:Q.dash.summary,swatches:E.map((S,O)=>({value:S,mark:vt[O%vt.length]?"\u2013 \u2013":"\u2014\u2014"}))}),k&&A?.ok&&i.push({channel:"lane",field:k.field,kind:"dimension",summary:Q.lane.summary,swatches:w.map((S,O)=>({value:S,mark:`lane ${O+1}`}))}),P&&i.push({channel:"coupling",field:P.field,kind:"dimension",summary:Q.coupling.summary,swatches:C.map(S=>({value:S,mark:Qe(S,P)}))}),L&&i.push({channel:"distance",field:L.field,kind:N?"measure":"dimension",summary:N?L.field==="share"?"coupled services sit together: the more of its traffic a caller sends down an edge, the shorter it is":"heavier flows pull their endpoints together":"things sharing this value cluster together",swatches:[]});for(let[S,O]of[["density",T],["speed",I],["radius",G],["halo",Te]])O&&i.push({channel:S,field:O.field,kind:"measure",summary:Q[S].summary,swatches:[]});if(K&&Ce?.ok){let S=X(K);i.push({channel:"nodeFill",field:K.field,kind:"dimension",summary:Q.nodeFill.summary,swatches:Ue.map((O,D)=>({value:O,color:S[D%S.length]}))})}return _&&$e?.ok&&i.push({channel:"column",field:_.field,kind:"dimension",summary:Q.column.summary,swatches:Ee.map((S,O)=>({value:S,mark:`col ${O+1}`}))}),{edges:h,nodes:le,clusterBy:x,legend:i,issues:o}}function Yt(r,e){return r.map?.[e]}function Qe(r,e){let s=e?.map?.[r];if(s==="rigid"||s==="breakaway"||s==="plain")return s;let t=r.toLowerCase().replace(/[\s_]/g,"-");return t.includes("fail-closed")||t==="closed"||t==="hard"||t==="required"||t==="critical"?"rigid":t.includes("fail-open")||t==="open"||t==="soft"||t==="optional"||t==="best-effort"?"breakaway":"plain"}function ns(r,e,s){let t=gn(r),n=gn(s.surface),i=Math.round(t.r+(n.r-t.r)*e),o=Math.round(t.g+(n.g-t.g)*e),a=Math.round(t.b+(n.b-t.b)*e);return`rgb(${i}, ${o}, ${a})`}function gn(r){let e=r.replace("#",""),s=e.length===3?e.split("").map(n=>n+n).join(""):e,t=Number.parseInt(s,16);return{r:t>>16&255,g:t>>8&255,b:t&255}}function Mt(r,e,s,t){let n=new Map,i=new Map;for(let u of r.nodes)n.set(u.id,"ok");let o=new Set(r.nodes.map(u=>u.id));for(let u of e)o.has(u)&&(n.set(u,"dead"),i.set(u,0));let a=u=>{if(!t)return"breakaway";let g=s.get(u)?.coupling??"plain";return g==="plain"?"breakaway":g};for(let u=0;u<r.nodes.length+1;u++){let g=!1;for(let m of r.edges){if(m.from===m.to)continue;let f=n.get(m.to)??"ok",v=n.get(m.from)??"ok";if(v==="dead")continue;let R=a(m.id);R==="rigid"&&f==="dead"?(n.set(m.from,"dead"),i.set(m.from,Math.min(i.get(m.from)??1/0,(i.get(m.to)??0)+1)),g=!0):v==="ok"&&(R==="breakaway"&&f!=="ok"||R==="rigid"&&f==="degraded")&&(n.set(m.from,"degraded"),i.set(m.from,Math.min(i.get(m.from)??1/0,(i.get(m.to)??0)+1)),g=!0)}if(!g)break}let l=new Map,d=[],c=0,h=0,p=0;for(let u of r.edges){p+=u.metrics.rps;let g=n.get(u.to)??"ok",m=n.get(u.from)??"ok";g==="dead"||m==="dead"?(l.set(u.id,"severed"),c+=u.metrics.rps,g==="dead"&&m!=="dead"&&a(u.id)==="breakaway"&&d.push(u.id)):g==="degraded"||m==="degraded"?(l.set(u.id,"strained"),h+=u.metrics.rps):l.set(u.id,"flowing")}return{health:n,edgeHealth:l,hop:i,firebreaks:d,deadRps:c,degradedRps:h,totalRps:p}}function Ze(r){let e=new Map;for(let n of r.nodes)e.set(n.id,"ok");let s=new Map,t=0;for(let n of r.edges)s.set(n.id,"flowing"),t+=n.metrics.rps;return{health:e,edgeHealth:s,hop:new Map,firebreaks:[],deadRps:0,degradedRps:0,totalRps:t}}function St(r){return r.gauges.length?r.gauges.length*5+3:0}function xe(r,e){let s=St(e);return{x:r.cx-r.w/2,y:r.top,w:r.w,h:Math.max(r.h-s,12)}}function oe(r,e,s){if(!s.gauges.length)return;let t=e.w*.82,n=e.cx-t/2,i=e.top+e.h-St(s)+3;for(let o of s.gauges)r.fillStyle=s.muted,r.globalAlpha=.28,r.fillRect(n,i,t,3),r.globalAlpha=1,r.fillStyle=o.value>.85?s.danger:o.value>.7?s.accent:s.stroke,r.fillRect(n,i,Math.max(1,t*is(o.value)),3),i+=5}function ss(r){return r<=1?1:Math.min(5,1+Math.floor(Math.log2(r)))}function kt(r,e,s,t){let n=xe(e,s),i=ss(s.instances),o=Math.min(4,n.w*.06);for(let a=i-1;a>=0;a--){let l=a*o;r.globalAlpha=a===0?1:.42-a*.05,t({x:n.x+l,y:n.y-l,w:n.w-l,h:n.h-l},a===0)}r.globalAlpha=1}function We(r,e,s,t,n,i){let o=Math.max(0,Math.min(i,t/2,n/2));r.beginPath(),r.moveTo(e+o,s),r.arcTo(e+t,s,e+t,s+n,o),r.arcTo(e+t,s+n,e,s+n,o),r.arcTo(e,s+n,e,s,o),r.arcTo(e,s,e+t,s,o),r.closePath()}function et(r,e,s,t,n){r.beginPath(),r.ellipse(e,s,Math.max(t,1),Math.max(n,1),0,0,Math.PI*2)}function ae(r,e){r.fillStyle=e.fill,r.strokeStyle=e.stroke,r.lineWidth=1.2}var ue={"service-rack":{name:"service-rack",summary:"a rack of compute; more instances is a deeper stack and a wider body",tierHint:2,draw(r,e,s){kt(r,e,s,(t,n)=>{if(ae(r,s),We(r,t.x,t.y,t.w,t.h,3),r.fill(),r.stroke(),!n)return;r.strokeStyle=s.muted,r.lineWidth=1;let i=Math.max(2,Math.min(4,Math.floor(t.h/7)));for(let o=1;o<=i;o++){let a=t.y+t.h/(i+1)*o;r.beginPath(),r.moveTo(t.x+4,a),r.lineTo(t.x+t.w-9,a),r.stroke()}r.fillStyle=s.health==="dead"?s.danger:s.accent,r.fillRect(t.x+t.w-6,t.y+4,2.5,2.5)}),oe(r,e,s)}},"gateway-arch":{name:"gateway-arch",summary:"an entry point traffic passes through",tierHint:1,draw(r,e,s){let t=xe(e,s);ae(r,s),We(r,t.x,t.y,t.w,t.h,3),r.fill(),r.stroke(),r.fillStyle=s.ground;let n=t.w*.44,i=t.h*.62;r.beginPath(),r.moveTo(t.x+(t.w-n)/2,t.y+t.h),r.lineTo(t.x+(t.w-n)/2,t.y+t.h-i+n/2),r.arc(t.x+t.w/2,t.y+t.h-i+n/2,n/2,Math.PI,0),r.lineTo(t.x+(t.w+n)/2,t.y+t.h),r.closePath(),r.fill(),r.strokeStyle=s.accent,r.lineWidth=1.4,r.stroke(),oe(r,e,s)}},"client-terminal":{name:"client-terminal",summary:"callers outside the system",tierHint:0,draw(r,e,s){let t=xe(e,s),n=t.h*.74;ae(r,s),We(r,t.x,t.y,t.w,n,3),r.fill(),r.stroke(),r.fillStyle=s.accent,r.fillRect(t.x+5,t.y+5,Math.max(3,t.w*.22),2.5),r.globalAlpha=.6,r.fillRect(t.x+5,t.y+10,Math.max(3,t.w*.4),2.5),r.globalAlpha=1,r.strokeStyle=s.stroke,r.beginPath(),r.moveTo(t.x+t.w/2,t.y+n),r.lineTo(t.x+t.w/2,t.y+t.h-2),r.moveTo(t.x+t.w*.28,t.y+t.h-2),r.lineTo(t.x+t.w*.72,t.y+t.h-2),r.stroke(),oe(r,e,s)}},"db-cylinder":{name:"db-cylinder",summary:"a relational store",tierHint:4,draw(r,e,s){kt(r,e,s,(t,n)=>{let i=Math.min(6,t.h*.17);ae(r,s),r.beginPath(),r.moveTo(t.x,t.y+i),r.lineTo(t.x,t.y+t.h-i),r.ellipse(t.x+t.w/2,t.y+t.h-i,t.w/2,i,0,Math.PI,0,!0),r.lineTo(t.x+t.w,t.y+i),r.closePath(),r.fill(),r.stroke(),et(r,t.x+t.w/2,t.y+i,t.w/2,i),r.fill(),r.stroke(),n&&(r.strokeStyle=s.muted,r.lineWidth=1,et(r,t.x+t.w/2,t.y+i+(t.h-i*2)*.45,t.w/2,i),r.stroke())}),oe(r,e,s)}},"db-discs":{name:"db-discs",summary:"a wide-column or analytics store: separated discs, not one barrel",tierHint:4,draw(r,e,s){let t=xe(e,s),n=Math.min(5,t.h*.14),i=3;ae(r,s);for(let o=0;o<i;o++){let a=t.y+n+(t.h-n*2)/(i-1)*o;et(r,t.x+t.w/2,a,t.w/2,n),r.fill(),r.stroke()}oe(r,e,s)}},"db-cube":{name:"db-cube",summary:"a key-value store: one addressable block",tierHint:4,draw(r,e,s){kt(r,e,s,t=>{let n=Math.min(8,t.w*.2),i=t.w-n,o=t.h-n;ae(r,s),r.fillRect(t.x,t.y+n,i,o),r.strokeRect(t.x,t.y+n,i,o),r.beginPath(),r.moveTo(t.x,t.y+n),r.lineTo(t.x+n,t.y),r.lineTo(t.x+i+n,t.y),r.lineTo(t.x+i,t.y+n),r.closePath(),r.fill(),r.stroke(),r.beginPath(),r.moveTo(t.x+i,t.y+n),r.lineTo(t.x+i+n,t.y),r.lineTo(t.x+i+n,t.y+o),r.lineTo(t.x+i,t.y+n+o),r.closePath(),r.fill(),r.stroke()}),oe(r,e,s)}},"db-drum":{name:"db-drum",summary:"an append-only log or stream",tierHint:4,draw(r,e,s){let t=xe(e,s),n=Math.min(7,t.w*.16);ae(r,s),r.beginPath(),r.moveTo(t.x+n,t.y),r.lineTo(t.x+t.w-n,t.y),r.ellipse(t.x+t.w-n,t.y+t.h/2,n,t.h/2,0,-Math.PI/2,Math.PI/2),r.lineTo(t.x+n,t.y+t.h),r.closePath(),r.fill(),r.stroke(),et(r,t.x+n,t.y+t.h/2,n,t.h/2),r.fill(),r.stroke(),r.strokeStyle=s.muted;for(let i of[.42,.68])r.beginPath(),r.ellipse(t.x+n+(t.w-n*2)*i,t.y+t.h/2,n*.8,t.h/2-1,0,-Math.PI/2,Math.PI/2),r.stroke();oe(r,e,s)}},"cache-chip":{name:"cache-chip",summary:"in-memory cache: a chip with pins",tierHint:3,draw(r,e,s){kt(r,e,s,(t,n)=>{let i=Math.min(5,t.w*.12);if(ae(r,s),We(r,t.x+i,t.y+2,t.w-i*2,t.h-4,2),r.fill(),r.stroke(),!n)return;r.strokeStyle=s.stroke,r.lineWidth=1.1;let o=Math.max(2,Math.min(4,Math.floor((t.h-8)/6)));for(let a=0;a<o;a++){let l=t.y+6+(t.h-12)/Math.max(o-1,1)*a;r.beginPath(),r.moveTo(t.x+i,l),r.lineTo(t.x,l),r.moveTo(t.x+t.w-i,l),r.lineTo(t.x+t.w,l),r.stroke()}r.strokeStyle=s.accent,We(r,t.x+i+4,t.y+6,t.w-i*2-8,t.h-12,1),r.stroke()}),oe(r,e,s)}},"queue-pipe":{name:"queue-pipe",summary:"a queue: messages waiting in a pipe",tierHint:3,draw(r,e,s){let t=xe(e,s);ae(r,s),We(r,t.x,t.y+t.h*.22,t.w,t.h*.56,t.h*.28),r.fill(),r.stroke(),r.fillStyle=s.accent;for(let n=0;n<3;n++){let i=t.h*.2;r.fillRect(t.x+6+n*(i+3),t.y+t.h/2-i/2,i,i)}oe(r,e,s)}},"blob-bucket":{name:"blob-bucket",summary:"object storage",tierHint:4,draw(r,e,s){let t=xe(e,s),n=Math.min(5,t.h*.15);ae(r,s),r.beginPath(),r.moveTo(t.x,t.y+n),r.lineTo(t.x+t.w*.16,t.y+t.h-n),r.ellipse(t.x+t.w/2,t.y+t.h-n,t.w*.34,n*.8,0,Math.PI,0,!0),r.lineTo(t.x+t.w,t.y+n),r.closePath(),r.fill(),r.stroke(),et(r,t.x+t.w/2,t.y+n,t.w/2,n),r.fill(),r.stroke(),oe(r,e,s)}},"external-cloud":{name:"external-cloud",summary:"something outside your control",tierHint:0,draw(r,e,s){let t=xe(e,s);ae(r,s),r.setLineDash([4,3]);let n=t.h*.34;r.beginPath(),r.arc(t.x+t.w*.3,t.y+t.h*.6,n,Math.PI*.5,Math.PI*1.5),r.arc(t.x+t.w*.45,t.y+t.h*.4,n*.9,Math.PI,Math.PI*1.85),r.arc(t.x+t.w*.72,t.y+t.h*.6,n,Math.PI*1.5,Math.PI*.5),r.closePath(),r.fill(),r.stroke(),r.setLineDash([]),oe(r,e,s)}}},rs=[[/postgres|mysql|maria|aurora|spanner|cockroach|sql|rdbms|relational/i,"db-cylinder"],[/cassandra|bigtable|scylla|clickhouse|druid|column|olap|warehouse|snowflake|bigquery/i,"db-discs"],[/dynamo|rocksdb|leveldb|etcd|kv|key-?value|riak/i,"db-cube"],[/kafka|kinesis|pulsar|log|stream|wal|event/i,"db-drum"],[/redis|memcache|cache/i,"cache-chip"],[/sqs|rabbit|queue|celery|nsq/i,"queue-pipe"],[/s3|gcs|blob|object|bucket|minio/i,"blob-bucket"]];function Rt(r,e){if(e&&ue[e])return e;let s=[r.attrs?.component,...Object.values(r.key),...Object.values(r.spans).flatMap(t=>t.length===1?t:[])].filter(t=>typeof t=="string");for(let t of s){if(ue[t])return t;for(let[n,i]of rs)if(n.test(t))return i}switch(r.kind){case"client":return"client-terminal";case"gateway":return"gateway-arch";case"cache":return"cache-chip";case"queue":return"queue-pipe";case"datastore":return"db-cylinder";case"external":return"external-cloud";default:return"service-rack"}}function Be(r){switch(r.kind){case"client":case"external":case"gateway":return 0;case"service":return 1;case"cache":case"queue":case"datastore":return 2;default:return 1}}var Et=["edge","services","data"];function yn(r){return{stroke:r.border,muted:r.textMuted,ground:r.background,danger:r.error,accent:r.flow.request}}function is(r){return r<0?0:r>1?1:r}function bn(r,e,s,t){let n=1-t;return{x:n*n*r.x+2*n*t*e.x+t*t*s.x,y:n*n*r.y+2*n*t*e.y+t*t*s.y}}function Tt(r,e,s=4){let t=e.x-r.x,n=e.y-r.y;if(t===0&&n===0)return{x:r.x,y:r.y};let i=r.width/2+s,o=r.height/2+s,a=Math.min(i/Math.abs(t||1e-6),o/Math.abs(n||1e-6));return{x:r.x+t*a,y:r.y+n*a}}function Pt(r,e,s=0){if(r.id===e.id)return os(r);let t=Tt(r,{x:e.x,y:e.y}),n=Tt(e,{x:r.x,y:r.y}),i=(t.x+n.x)/2,o=(t.y+n.y)/2,a=n.x-t.x,l=n.y-t.y,d=Math.hypot(a,l)||1,c={x:i+-l/d*s*2,y:o+a/d*s*2};return wn(t,n,c)}function os(r){let e={x:r.x+r.width/2,y:r.y-r.height/4},s={x:r.x+r.width/2,y:r.y+r.height/4},t={x:r.x+r.width/2+54,y:r.y};return wn(e,s,t)}function wn(r,e,s){let n=0,i=r;for(let o=1;o<=24;o++){let a=bn(r,s,e,o/24);n+=Math.hypot(a.x-i.x,a.y-i.y),i=a}return{a:r,b:e,ctrl:s,length:n||1,pointAt:o=>bn(r,s,e,o),tangentAt:o=>{let a=1-o,l=2*a*(s.x-r.x)+2*o*(e.x-s.x),d=2*a*(s.y-r.y)+2*o*(e.y-s.y),c=Math.hypot(l,d)||1;return{x:l/c,y:d/c}}}}function At(r,e,s=20){let t=1/0,n=r.pointAt(0);for(let i=1;i<=s;i++){let o=r.pointAt(i/s);t=Math.min(t,as(e,n,o)),n=o}return t}function as(r,e,s){let t=s.x-e.x,n=s.y-e.y,i=t*t+n*n;if(i===0)return Math.hypot(r.x-e.x,r.y-e.y);let o=((r.x-e.x)*t+(r.y-e.y)*n)/i;return o=o<0?0:o>1?1:o,Math.hypot(r.x-(e.x+o*t),r.y-(e.y+o*n))}var He=class{constructor(e,s=Math.random){this.particles=[];this.accumulator=0;this.emitPerSecond=0;this.speedPxPerSecond=0;this.radius=2;this.stopped=!1;this.overrideErrorRate=null;this.spreadPx=4.2;this.phased=!1;this.edge=e,this.rand=s}configure(e,s=ne){this.emitPerSecond=this.stopped?0:be(this.edge.metrics.rps,e,s),this.speedPxPerSecond=we(this.edge.metrics.latencyMs,s),this.radius=De(this.edge.metrics.bytes,s)}configureDirect(e,s,t,n){this.emitPerSecond=this.stopped?0:e,this.speedPxPerSecond=s,this.radius=t,this.overrideErrorRate=n}setLane(e,s=0){this.spreadPx=e,this.phased||(this.accumulator=s,this.phased=!0)}stopEmitting(){this.stopped=!0,this.emitPerSecond=0}resumeEmitting(){this.stopped=!1}get emitRate(){return this.emitPerSecond}get speed(){return this.speedPxPerSecond}step(e,s,t=ne,n=1){let i=this.overrideErrorRate??this.edge.metrics.errorRate;if(this.emitPerSecond>0){this.accumulator+=this.emitPerSecond*e*n;let l=Math.min(Math.floor(this.accumulator),Math.max(0,t.maxParticlesPerEdge-this.particles.length),120);this.accumulator-=Math.floor(this.accumulator);for(let d=0;d<l;d++)this.particles.push({t:-d/Math.max(l,1)*(this.speedPxPerSecond*e)/s.length,speed:this.speedPxPerSecond*(.88+this.rand()*.24),radius:this.radius*(.85+this.rand()*.3),error:this.rand()<i,wobble:(this.rand()-.5)*this.spreadPx})}let o=0,a=[];for(let l of this.particles){if(l.t+=l.speed*e*n/s.length,l.t>=1){o++;continue}a.push(l)}return this.particles=a,o}clear(){this.particles.length=0,this.accumulator=0,this.stopped=!1,this.phased=!1}};function ee(r){let e=r>>>0;return()=>{e=e+1831565813>>>0;let s=Math.imul(e^e>>>15,1|e);return s=s+Math.imul(s^s>>>7,61|s)^s,((s^s>>>14)>>>0)/4294967296}}function Me(r,e){let s=Math.cos(e.yaw),t=Math.sin(e.yaw),n=r.x*s-r.z*t,i=r.x*t+r.z*s;return{x:e.cx+n*e.scale,y:e.cy-r.y*e.scaleY+i*e.scaleY*e.tilt,depth:i}}var ke={tierOf:Be,attractionOf:()=>.5,tierGap:1.4,nodeWidth:62,nodeHeight:52,iterations:320,seed:7,tilt:.3,gap:1.2,labelSpace:26};function Ot(r,e={}){let s={...ke,...e},t=ee(s.seed),n=r.nodes;if(!n.length)return new Map;let i=n.map(b=>s.tierOf(b)),o=Math.max(...i,0),a=new Map;for(let b of i)a.set(b,(a.get(b)??0)+1);let l=new Map,d=[],c=[];for(let[b,k]of n.entries()){let w=i[b],A=l.get(w)??0;l.set(w,A+1);let L=a.get(w)??1,N=A/L*Math.PI*2+w*.7,y=.55+(t()-.5)*.12;d.push(Math.cos(N)*y),c.push(Math.sin(N)*y)}let h=new Map(n.map((b,k)=>[b.id,k])),p=[];for(let b of r.edges){let k=h.get(b.from),w=h.get(b.to);k===void 0||w===void 0||k===w||p.push({a:k,b:w,w:s.attractionOf(b.id)})}if(s.groupOf){let b=new Map;for(let[k,w]of n.entries()){let A=s.groupOf(w);A&&(b.get(A)??b.set(A,[]).get(A)).push(k)}for(let k of b.values())for(let w=0;w<k.length;w++)for(let A=w+1;A<k.length;A++)p.push({a:k[w],b:k[A],w:.55})}let u=n.length,g=new Float64Array(u),m=new Float64Array(u);for(let b=0;b<s.iterations;b++){let k=1-b/s.iterations;g.fill(0),m.fill(0);for(let w=0;w<u;w++)for(let A=w+1;A<u;A++){let L=d[w]-d[A],N=c[w]-c[A],y=L*L+N*N;y<1e-6&&(L=(t()-.5)*.01,N=(t()-.5)*.01,y=1e-4);let P=(i[w]===i[A]?.02:.006)/y,C=Math.sqrt(y);g[w]+=L/C*P,m[w]+=N/C*P,g[A]-=L/C*P,m[A]-=N/C*P}for(let w of p){let A=d[w.b]-d[w.a],L=c[w.b]-c[w.a],N=.02*(.15+w.w);g[w.a]+=A*N,m[w.a]+=L*N,g[w.b]-=A*N,m[w.b]-=L*N}for(let w=0;w<u;w++)g[w]-=d[w]*.006,m[w]-=c[w]*.006,d[w]+=g[w]*k*2.2,c[w]+=m[w]*k*2.2}for(let b=0;b<u;b++)c[b]*=.42;ds(d,c,i,t,s.tilt,s.gap);let f=d.reduce((b,k)=>b+k,0)/u,v=c.reduce((b,k)=>b+k,0)/u,R=()=>{let b=.2;for(let k=0;k<u;k++)b=Math.max(b,Math.abs(c[k]));return Math.max(s.tierGap,2*b*s.tilt+.62)},E=()=>{let b=R(),k=new Map;for(let[w,A]of n.entries()){let L=s.sizeOf?.(A)??{w:s.nodeWidth,h:s.nodeHeight};k.set(A.id,{id:A.id,x:d[w]-f,z:c[w]-v,y:(o-i[w])*b,tier:i[w],w:L.w,h:L.h})}return k};if(s.viewport)for(let b=0;b<5;b++){let k=E(),w=tt(k,s.viewport.width,s.viewport.height,s.viewport.yaw,s.tilt,void 0,qe(k));if(!ls(d,c,i,n,k,w,s))break}return E()}function ls(r,e,s,t,n,i,o){let a=t.length,l=t.map((u,g)=>{let m=n.get(u.id),f=Me(m,i);return{i:g,x:f.x,y:f.y,w:m.w,h:m.h+o.labelSpace,tier:s[g]}}),d=Math.cos(i.yaw),c=Math.sin(i.yaw),h=(u,g)=>{let m=u/i.scale,f=g/(i.scaleY*i.tilt);return{dx:m*d+f*c,dz:-m*c+f*d}},p=!1;for(let u=0;u<24;u++){let g=!1;for(let m=0;m<a;m++)for(let f=m+1;f<a;f++){let v=l[m],R=l[f],E=12,b=v.tier===R.tier?8:2,k=(v.w+R.w)/2+E,w=(v.h+R.h)/2+b,A=R.x-v.x,L=R.y-v.y,N=k-Math.abs(A),y=w-Math.abs(L);if(N<=0||y<=0)continue;let x=0,P=0;N/k<y/w*2.2?x=(A>=0?1:-1)*N*.5:P=(L>=0?1:-1)*y*.5;let{dx:C,dz:T}=h(x,P);r[m]-=C*.55,e[m]-=T*.55,r[f]+=C*.55,e[f]+=T*.55,v.x-=x*.55,v.y-=P*.55,R.x+=x*.55,R.y+=P*.55,g=!0,p=!0}if(!g)break}return p}function qe(r,e=.35){let s=.6;for(let t of r.values())s=Math.max(s,Math.abs(t.x),Math.abs(t.z));return s+e}function ds(r,e,s,t,n,i){let o=r.length,a=Math.max(n,.15);for(let l=0;l<90;l++){let d=0;for(let c=0;c<o;c++)for(let h=c+1;h<o;h++){let u=s[c]===s[h]?i:i*.3,g=r[h]-r[c],m=(e[h]-e[c])*a,f=Math.hypot(g,m);if(f<1e-5&&(g=t()-.5,m=t()-.5,f=Math.hypot(g,m)||1),f>=u)continue;let v=(u-f)/f*.5;r[c]-=g*v,e[c]-=m/a*v,r[h]+=g*v,e[h]+=m/a*v,d=Math.max(d,u-f)}if(d<.004)break}}function tt(r,e,s,t,n=.3,i=44,o){let a={yaw:t,tilt:n,scale:1,scaleY:1,cx:0,cy:0},l=1/0,d=-1/0,c=1/0,h=-1/0,p=[...r.values()].map(L=>L.y),u=[...r.values()].map(L=>({x:L.x,y:L.y,z:L.z}));if(o&&p.length)for(let L of[Math.min(...p),Math.max(...p)])for(let N of[-o,o])for(let y of[-o,o])u.push({x:N,y:L,z:y});for(let L of u){let N=Me(L,a);l=Math.min(l,N.x),d=Math.max(d,N.x),c=Math.min(c,N.y),h=Math.max(h,N.y)}if(!Number.isFinite(l))return{yaw:t,tilt:n,scale:100,scaleY:100,cx:e/2,cy:s/2};let g=Math.max(e-i*2,120),m=Math.max(s-i*2-44,120),f=g/Math.max(d-l,.001),v=m/Math.max(h-c,.001),R=Math.min(f,v),E=1.6,b=Math.min(f,R*E),k=Math.min(v,R*E),w=(l+d)/2,A=(c+h)/2;return{yaw:t,tilt:n,scale:b,scaleY:k,cx:e/2-w*b,cy:s/2-A*k}}var nt={width:960,height:540,padding:48,nodeWidth:132,nodeHeight:46,layerGap:64,rowGap:26};function Lt(r,e={}){let s={...nt,...e},t=r.nodes.map(T=>T.id),n=new Map(t.map((T,F)=>[T,F])),i=new Map(t.map(T=>[T,[]])),o=new Map(t.map(T=>[T,[]])),a=r.edges.filter(T=>T.from!==T.to&&T.kind!=="response"&&T.kind!=="challenge"),l=a.length?a:r.edges.filter(T=>T.from!==T.to);for(let T of l){let F=.15+(s.weightOf&&T.id?s.weightOf(T.id):.5);i.get(T.from).push({id:T.to,w:F}),o.get(T.to).push({id:T.from,w:F})}let d=new Map;for(let T of r.nodes)typeof T.layer=="number"&&d.set(T.id,T.layer);let c=new Map,h=T=>{let F=d.get(T);if(F!==void 0&&c.get(T)===2)return F;if(c.get(T)===1)return F??0;c.set(T,1);let I=F??0;if(F===void 0)for(let H of o.get(T)??[])I=Math.max(I,h(H.id)+1);return d.set(T,I),c.set(T,2),I};for(let T of t)h(T);let p=Math.max(0,...d.values()),u=Array.from({length:p+1},()=>[]);for(let T of t)u[d.get(T)].push(T);let g=new Map;for(let T of u)T.forEach((F,I)=>g.set(F,I));let m=(T,F)=>{let I=F.get(T)??[];if(!I.length)return g.get(T)??0;let H=0,G=0;for(let B of I)H+=(g.get(B.id)??0)*B.w,G+=B.w;return H/G};for(let T=0;T<4;T++){let F=T%2===0?o:i,I=T%2===0?u:[...u].reverse();for(let H of I){let G=H.map(B=>({id:B,key:m(B,F),tie:n.get(B)??0}));G.sort((B,X)=>B.key-X.key||B.tie-X.tie),G.forEach((B,X)=>g.set(B.id,X)),H.sort((B,X)=>g.get(B)-g.get(X))}}if(s.rowKey)for(let T of u)T.sort((F,I)=>{let H=s.rowKey(F),G=s.rowKey(I);return typeof H=="number"&&typeof G=="number"?G-H:String(H).localeCompare(String(G))});let f=u.length,v=Math.max(s.width-s.padding*2,120),E=Math.max(64,Math.min(s.nodeWidth,(v-(f-1)*36)/Math.max(f,1))),b=8,k=Math.max(1,...u.map(T=>T.length)),w=Math.max(s.height-s.padding*2,80),A=Math.max(22,Math.min(s.nodeHeight,(w-(k-1)*b)/k)),L=Math.max(v-E,1),N=Math.max(w-A,1),y=s.padding+E/2,x=s.padding+A/2,P=f>1?L/(f-1):0,C=new Map;for(let[T,F]of u.entries()){let I=F.length,H=I>1?Math.min(N/(I-1),Math.max(A+s.rowGap,N*.55)):0,G=H*(I-1),B=x+(N-G)/2;for(let[X,le]of F.entries()){let K=r.nodes[n.get(le)];C.set(le,{id:le,layer:T,x:K.x??(f>1?y+T*P:s.width/2),y:K.y??(I>1?B+X*H:s.height/2),width:E,height:A})}}return s.layerGap,C}var Se=class extends Error{constructor(e){super(`Invalid graph:
|
|
2
|
+
- ${e.join(`
|
|
3
|
+
- `)}`),this.name="GraphValidationError",this.issues=e}},vn=new Set(["client","gateway","service","datastore","cache","queue","external"]),xn=new Set(["request","response","retry","challenge","async"]);function Ct(r){return typeof r=="number"&&Number.isFinite(r)}function st(r){let e=[];if(!r||typeof r!="object")throw new Se(["graph must be an object"]);if(Array.isArray(r.nodes)||e.push("nodes must be an array"),Array.isArray(r.edges)||e.push("edges must be an array"),e.length)throw new Se(e);let s=new Set,t=[];for(let[o,a]of r.nodes.entries()){let l=a;if(!l||typeof l.id!="string"||l.id===""){e.push(`nodes[${o}]: id must be a non-empty string`);continue}if(s.has(l.id)){e.push(`nodes[${o}]: duplicate id ${JSON.stringify(l.id)}`);continue}s.add(l.id),l.kind!==void 0&&!vn.has(l.kind)&&e.push(`nodes[${o}] (${l.id}): unknown kind ${JSON.stringify(l.kind)}; expected one of ${[...vn].join(", ")}`),t.push({...l,label:l.label??l.id,kind:l.kind??"service"})}let n=[],i=new Set;for(let[o,a]of r.edges.entries()){let l=a;if(!l||typeof l!="object"){e.push(`edges[${o}]: must be an object`);continue}s.has(l.from)||e.push(`edges[${o}]: unknown from-node ${JSON.stringify(l.from)}`),s.has(l.to)||e.push(`edges[${o}]: unknown to-node ${JSON.stringify(l.to)}`),l.kind!==void 0&&!xn.has(l.kind)&&e.push(`edges[${o}]: unknown kind ${JSON.stringify(l.kind)}; expected one of ${[...xn].join(", ")}`);let d=l.metrics;if(!d||typeof d!="object"){e.push(`edges[${o}] (${l.from}->${l.to}): metrics is required`);continue}(!Ct(d.rps)||d.rps<0)&&e.push(`edges[${o}] (${l.from}->${l.to}): metrics.rps must be a number >= 0`),d.latencyMs!==void 0&&(!Ct(d.latencyMs)||d.latencyMs<0)&&e.push(`edges[${o}] (${l.from}->${l.to}): metrics.latencyMs must be a number >= 0`),d.errorRate!==void 0&&(!Ct(d.errorRate)||d.errorRate<0||d.errorRate>1)&&e.push(`edges[${o}] (${l.from}->${l.to}): metrics.errorRate must be between 0 and 1`),d.bytes!==void 0&&(!Ct(d.bytes)||d.bytes<0)&&e.push(`edges[${o}] (${l.from}->${l.to}): metrics.bytes must be a number >= 0`);let c=l.id??`${l.from}->${l.to}`;if(i.has(c)){let h=2;for(;i.has(`${c}#${h}`);)h++;c=`${c}#${h}`}i.add(c),n.push({...l,id:c,kind:l.kind??"request",metrics:{rps:d.rps,latencyMs:d.latencyMs??25,errorRate:d.errorRate??0,bytes:d.bytes??1024}})}if(e.length)throw new Se(e);return{version:1,nodes:t,edges:n,meta:r.meta??{}}}function Mn(r){try{return{ok:!0,graph:st(r)}}catch(e){return e instanceof Se?{ok:!1,issues:e.issues}:{ok:!1,issues:[e.message]}}}function $t(r){let e=st(r),s=new Map,t=new Map;for(let n of e.edges)s.set(n.to,(s.get(n.to)??0)+n.metrics.rps),t.set(n.from,(t.get(n.from)??0)+n.metrics.rps);return{nodes:e.nodes.map(n=>({id:n.id,label:n.label,kind:n.kind,key:{id:n.id},spans:{},inboundRps:s.get(n.id)??0,outboundRps:t.get(n.id)??0,attrs:{},records:1,...n.layer!==void 0?{layer:n.layer}:{},...n.x!==void 0?{x:n.x}:{},...n.y!==void 0?{y:n.y}:{}})),edges:e.edges.map(n=>({id:n.id,from:n.from,to:n.to,kind:n.kind,dims:{kind:n.kind},spans:{},metrics:{...n.metrics,share:(t.get(n.from)??0)>0?n.metrics.rps/t.get(n.from):0},records:1,...n.label!==void 0?{label:n.label}:{}})),meta:e.meta,nodeKey:[]}}var rt={name:"dark",background:"#0e1117",surface:"#161b23",border:"#2a323d",text:"#e6edf3",textMuted:"#8b98a8",grid:"#1b222c",flow:{request:"#5aa9ff",response:"#4ad6b8",retry:"#ffb54d",challenge:"#c58bff",async:"#7f8fa6"},node:{client:"#2d3b52",gateway:"#3a3350",service:"#233145",datastore:"#1f3d3a",cache:"#40352a",queue:"#33304a",external:"#2b2f36"},nodeText:"#e6edf3",edge:"#55637a",edgeMuted:"#2c3541",error:"#ff6b6b",highlight:"#ffffff",tooltipBg:"#1c232d",tooltipText:"#e6edf3"},it={name:"light",background:"#fbfbfd",surface:"#ffffff",border:"#dfe3ea",text:"#1a1f27",textMuted:"#616c7d",grid:"#eef1f5",flow:{request:"#1f6feb",response:"#0d9488",retry:"#d97706",challenge:"#8b5cf6",async:"#64748b"},node:{client:"#e7edf9",gateway:"#eee9fb",service:"#eaf1fb",datastore:"#e4f4f0",cache:"#fcf1e2",queue:"#eeecfa",external:"#eef0f3"},nodeText:"#1a1f27",edge:"#9aa8ba",edgeMuted:"#d5dce6",error:"#dc2626",highlight:"#0b1220",tooltipBg:"#1c232d",tooltipText:"#f5f7fa"},kn={dark:rt,light:it};function me(r){return r&&typeof r=="object"?r:r==="light"?it:r==="dark"||typeof globalThis.matchMedia=="function"&&globalThis.matchMedia("(prefers-color-scheme: dark)").matches?rt:it}var Jt=new Set(["retry","challenge"]),cs=34,Ve=.95,Nt=class{constructor(e,s={}){this.insets={top:0,right:0,bottom:0,left:0};this.table=null;this.spec={nodeKey:[]};this.scene={nodes:[],edges:[],meta:{},nodeKey:[]};this.styles={edges:new Map,nodes:new Map,clusterBy:null,legend:[],issues:[]};this.failure=Ze({nodes:[],edges:[],meta:{},nodeKey:[]});this.splitBy=[];this.matched=0;this.filtered=0;this.targets=new Map;this.origins=new Map;this.positions=new Map;this.morphFrom=0;this.morphing=!1;this.views=[];this.killedAt=0;this.world=new Map;this.camera=null;this.yaw=.62;this.yawVelocity=0;this.dragging=null;this.selected=null;this.highlight=null;this.shocks=[];this.sprites=new Map;this.width=0;this.height=0;this.dpr=1;this.raf=0;this.lastFrame=0;this.running=!1;this.hovered=null;this.pointer=null;this.onMotionChange=()=>{this.opts.animate=!lt(),this.opts.morphMs=lt()?0:this.opts.morphMs,this.draw()};this.onThemeChange=()=>{this.themeSetting==="auto"&&(this.theme=me("auto"),this.applyScene(this.scene,this.spec,this.matched,this.filtered))};this.frame=()=>{if(!this.running)return;let e=re(),s=Math.min((e-this.lastFrame)/1e3,.1);if(this.lastFrame=e,this.morphing){let t=Math.min(1,(e-this.morphFrom)/this.opts.morphMs);this.positions=Sn(this.origins,this.targets,Ms(t));for(let n of this.views)n.path=this.pathFor(n.edge,n.offset);if(t>=1){this.morphing=!1,this.positions=new Map(this.targets);for(let n of this.views)n.path=this.pathFor(n.edge,n.offset);this.placeLabels()}}if(!this.dragging&&Math.abs(this.yawVelocity)>15e-5&&this.isWorld()?(this.yaw+=this.yawVelocity*s*60,this.yawVelocity*=Math.pow(.86,s*60),this.recamera()):this.isWorld()&&this.opts.orbit&&this.opts.animate&&!this.dragging&&!this.hovered&&!this.morphing&&(this.yaw+=.05*s*this.opts.speed,this.recamera()),this.shocks=this.shocks.filter(t=>e-t.at<1600),this.opts.animate&&this.opts.speed>0){let t=(e-this.killedAt)/1e3;for(let n of this.views)this.failure.edgeHealth.get(n.edge.id)==="severed"&&t>=this.frontArrival(n.edge)&&n.stream.stopEmitting(),n.stream.step(s,n.path,this.encoding,this.opts.speed)}this.draw(),this.raf=requestAnimationFrame(this.frame)};this.onPointerDown=e=>{this.isWorld()&&(this.dragging={x:e.clientX,yaw:this.yaw,lastX:e.clientX,lastT:re()},this.yawVelocity=0,this.canvas.setPointerCapture?.(e.pointerId))};this.onPointerUp=e=>{this.dragging=null,this.canvas.releasePointerCapture?.(e.pointerId)};this.onPointerMove=e=>{let s=this.canvas.getBoundingClientRect(),t=e.clientX-s.left,n=e.clientY-s.top;if(this.pointer={x:t,y:n},this.dragging){this.yaw=this.dragging.yaw+(e.clientX-this.dragging.x)*.005;let a=re(),l=Math.max(a-this.dragging.lastT,1);this.yawVelocity=(e.clientX-this.dragging.lastX)*.005/(l/16.7),this.dragging.lastX=e.clientX,this.dragging.lastT=a,this.recamera(),this.canvas.style.cursor="grabbing";return}let i=this.hitTest(t,n),o=En(i)!==En(this.hovered);this.hovered=i,this.canvas.style.cursor=i?"pointer":this.isWorld()?"grab":"default",this.updateTooltip(),o&&this.opts.onHover?.(i)};this.onPointerLeave=()=>{this.pointer=null,this.hovered&&this.opts.onHover?.(null),this.hovered=null,this.canvas.style.cursor="default",this.updateTooltip()};this.onClick=()=>{this.opts.onSelect?.(this.hovered)};this.container=e,this.themeSetting=s.theme??"auto",this.theme=me(this.themeSetting),this.encoding={...ne,...s.encoding},this.layoutOpts=s.layout??{},this.rand=ee(s.seed??1),this.insets={top:0,right:0,bottom:0,left:0,...s.insets},this.opts={showEdgeLabels:s.showEdgeLabels??!1,showLegend:s.showLegend??!0,speed:s.speed??1,animate:s.animate??!lt(),seed:s.seed??1,morphMs:s.morphMs??(lt()?0:900),orbit:s.orbit??!lt(),onHover:s.onHover,onSelect:s.onSelect,onView:s.onView},getComputedStyle(e).position==="static"&&(e.style.position="relative"),this.canvas=document.createElement("canvas"),this.canvas.style.display="block",this.canvas.style.width="100%",this.canvas.style.height="100%",this.canvas.setAttribute("role","img"),e.appendChild(this.canvas);let t=this.canvas.getContext("2d");if(!t)throw new Error("tracelight: 2d canvas context unavailable");this.ctx=t,this.tooltip=document.createElement("div"),Object.assign(this.tooltip.style,{position:"absolute",pointerEvents:"none",opacity:"0",transition:"opacity 90ms ease",font:"12px/1.45 ui-monospace, SFMono-Regular, Menlo, monospace",padding:"8px 10px",borderRadius:"7px",whiteSpace:"pre",zIndex:"2",maxWidth:"320px",boxShadow:"0 6px 20px rgba(0,0,0,.28)"}),e.appendChild(this.tooltip),this.canvas.addEventListener("pointerdown",this.onPointerDown),this.canvas.addEventListener("pointerup",this.onPointerUp),this.canvas.addEventListener("pointermove",this.onPointerMove),this.canvas.addEventListener("pointerleave",this.onPointerLeave),this.canvas.addEventListener("click",this.onClick),typeof ResizeObserver<"u"&&(this.resizeObserver=new ResizeObserver(()=>this.resize()),this.resizeObserver.observe(e)),typeof matchMedia=="function"&&(this.motionQuery=matchMedia("(prefers-reduced-motion: reduce)"),this.motionQuery.addEventListener?.("change",this.onMotionChange),this.themeQuery=matchMedia("(prefers-color-scheme: dark)"),this.themeQuery.addEventListener?.("change",this.onThemeChange)),this.resize()}setGraph(e){this.table=null,this.applyScene($t(e),this.spec,e.edges.length,0)}setTable(e,s){this.table=e,this.spec=s??this.spec,this.reproject()}setSpec(e){let s=JSON.stringify(e.killed??[])!==JSON.stringify(this.spec.killed??[]);this.spec=e,s&&(this.killedAt=re()),this.table?this.reproject():this.applyScene(this.scene,e,this.matched,this.filtered)}getSpec(){return this.spec}getScene(){return this.scene}getLegend(){return this.styles.legend}schema(){return this.table?mt(this.table):null}kill(e){let s=new Set(this.spec.killed??[]);for(let t of e)s.has(t)||this.shocks.push({id:t,at:re()});this.setSpec({...this.spec,killed:e})}health(e){return this.failure.health.get(e)??"ok"}select(e){this.selected=e,this.draw()}getSelected(){return this.selected}setHighlight(e){this.highlight=e,this.draw()}setYaw(e){this.yaw=e,this.isWorld()&&this.recamera()}getYaw(){return this.yaw}setOptions(e){e.theme!==void 0&&(this.themeSetting=e.theme,this.theme=me(e.theme)),e.encoding&&(this.encoding={...this.encoding,...e.encoding}),e.layout&&(this.layoutOpts={...this.layoutOpts,...e.layout}),e.showEdgeLabels!==void 0&&(this.opts.showEdgeLabels=e.showEdgeLabels),e.showLegend!==void 0&&(this.opts.showLegend=e.showLegend),e.speed!==void 0&&(this.opts.speed=e.speed),e.animate!==void 0&&(this.opts.animate=e.animate),e.morphMs!==void 0&&(this.opts.morphMs=e.morphMs),e.insets!==void 0&&(this.insets={top:0,right:0,bottom:0,left:0,...e.insets}),e.orbit!==void 0&&(this.opts.orbit=e.orbit),e.onHover!==void 0&&(this.opts.onHover=e.onHover),e.onSelect!==void 0&&(this.opts.onSelect=e.onSelect),e.onView!==void 0&&(this.opts.onView=e.onView),this.applyScene(this.scene,this.spec,this.matched,this.filtered)}stats(){let e=0,s=0;for(let i of this.scene.edges)e+=i.metrics.rps,Jt.has(i.kind)&&(s+=i.metrics.rps);let t=0,n=0;for(let i of this.failure.health.values())i==="dead"?t++:i==="degraded"&&n++;return{totalRps:e,wastedRps:s,wastedFraction:e>0?s/e:0,nodes:this.scene.nodes.length,edges:this.scene.edges.length,records:this.matched,filtered:this.filtered,deadRps:this.failure.deadRps,degradedRps:this.failure.degradedRps,deadNodes:t,degradedNodes:n}}start(){this.running||(this.running=!0,this.lastFrame=re(),this.raf=requestAnimationFrame(this.frame))}stop(){this.running=!1,this.raf&&cancelAnimationFrame(this.raf),this.raf=0}toDataURL(e="image/png"){return this.canvas.toDataURL(e)}destroy(){this.stop(),this.resizeObserver?.disconnect(),this.motionQuery?.removeEventListener?.("change",this.onMotionChange),this.themeQuery?.removeEventListener?.("change",this.onThemeChange),this.canvas.removeEventListener("pointerdown",this.onPointerDown),this.canvas.removeEventListener("pointerup",this.onPointerUp),this.canvas.removeEventListener("pointermove",this.onPointerMove),this.canvas.removeEventListener("pointerleave",this.onPointerLeave),this.canvas.removeEventListener("click",this.onClick),this.canvas.remove(),this.tooltip.remove()}reproject(){if(!this.table)return;let e=gt(this.table,this.spec);this.splitBy=e.splitBy,this.applyScene(e.scene,this.spec,e.matched,e.filtered)}applyScene(e,s,t,n){let i=this.scene,o=new Map(this.positions),a=this.views;this.scene=e,this.spec=s,this.matched=t,this.filtered=n,this.styles=xt(e,s,this.theme,this.encoding),this.selected&&!e.nodes.some(d=>d.id===this.selected)&&(this.selected=null);for(let d of e.nodes){let c=this.styles.nodes.get(d.id)?.column;c!=null?d.layer=c:e.nodeKey.length&&delete d.layer}let l=!!s.channels?.coupling;this.failure=s.killed?.length?Mt(e,s.killed,this.styles.edges,l):Ze(e),this.layout(i,o,a),this.opts.onView?.({scene:e,spec:s,legend:this.styles.legend,splitBy:this.splitBy,issues:this.styles.issues,stats:this.stats()})}layout(e,s,t){if(!this.scene.nodes.length){this.views=[],this.positions=new Map,this.targets=new Map,this.draw();return}if(this.isWorld()){let o=this.box();this.world=Ot(this.scene,{tierOf:a=>this.tierOf(a),attractionOf:a=>this.styles.edges.get(a)?.attraction??.5,sizeOf:a=>this.bodySize(a),viewport:{width:o.width,height:o.height,yaw:this.spec.world?.yaw??this.yaw},...this.styles.clusterBy?{groupOf:a=>ot(a,this.styles.clusterBy)}:{},seed:this.opts.seed}),this.yaw=this.spec.world?.yaw??this.yaw,this.targets=this.projectWorld()}else{this.world=new Map,this.camera=null;let o=this.box(),a=Lt(this.scene,{...nt,...this.layoutOpts,width:o.width,height:o.height,weightOf:l=>this.styles.edges.get(l)?.attraction??.5,...this.rowKey()?{rowKey:this.rowKey()}:{}});this.targets=new Map;for(let[l,d]of a)this.targets.set(l,{...d,x:d.x+o.left,y:d.y+o.top})}let n=us(e,this.scene);this.origins=new Map;for(let o of this.scene.nodes){let a=this.targets.get(o.id),d=(n.get(o.id)??[]).map(c=>s.get(c)).filter(Boolean);d.length?this.origins.set(o.id,{x:d.reduce((c,h)=>c+h.x,0)/d.length,y:d.reduce((c,h)=>c+h.y,0)/d.length,fade:1}):this.origins.set(o.id,{x:a.x,y:a.y,fade:0})}let i=this.opts.morphMs>0&&s.size>0&&[...n.values()].some(o=>o.length);this.morphing=i,this.morphFrom=re(),this.positions=i?Sn(this.origins,this.targets,0):new Map(this.targets),this.buildViews(e,t),i||this.placeLabels(),this.draw(),this.running||this.start()}buildViews(e,s){let t=ve(this.scene.edges.map(o=>o.metrics.rps)),n=vs(this.scene.edges,this.styles.edges),i=ps(e,this.scene,s);this.views=this.scene.edges.map(o=>{let a=this.styles.edges.get(o.id),l=i.get(o.id)??new He(ms(o),this.rand);l.configureDirect(a.density,a.speed,a.radius,o.metrics.errorRate);let d=n.get(o.id)??{offset:0,spread:4.2,phase:0};l.setLane(d.spread,d.phase);let c=d.offset;return{edge:o,style:a,stream:l,offset:c,spread:d.spread,width:Ie(o.metrics.rps,t,{curve:this.encoding.densityCurve}),path:this.pathFor(o,c),labelT:.5}})}isWorld(){return this.spec.mode==="world"}tierOf(e){let s=this.spec.world?.tierBy;if(s){let t=ot(e,s),n=this.spec.world?.tiers;if(t&&n?.length){let i=n.indexOf(t);if(i>=0)return i}if(t)return[...new Set(this.scene.nodes.map(o=>ot(o,s)??"\u2205"))].sort().indexOf(t)}return Be(e)}bodySize(e){let s=this.spec.world?.instances??"instances",t=Math.max(1,e.attrs[s]??1),n=ke,i=this.crowdScale();return{w:Math.min(n.nodeWidth*(1+Math.log2(t)*.09),n.nodeWidth*1.45)*i,h:Math.min(n.nodeHeight*(1+Math.log2(t)*.04),n.nodeHeight*1.3)*i}}crowdScale(){let e=this.scene.nodes.length;if(!e)return 1;let s=this.box(),t=s.width*s.height*.5,n=ke.nodeWidth*(ke.nodeHeight+ke.labelSpace);return xs(Math.sqrt(t/(e*n)),.5,1)}rowKey(){let e=this.spec.layout?.rowSort??"auto";if(e==="auto")return null;let s=new Map(this.scene.nodes.map(t=>[t.id,t]));return e==="name"?t=>s.get(t)?.label??t:t=>{let n=s.get(t);return n?n.inboundRps+n.outboundRps:0}}box(){let e=this.opts.showLegend?cs:0,{top:s,right:t,bottom:n,left:i}=this.insets;return{left:i,top:s,width:Math.max(this.width-i-t,200),height:Math.max(this.height-s-n-e,160)}}recamera(){this.targets=this.projectWorld(),this.positions=new Map(this.targets);for(let e of this.views)e.path=this.pathFor(e.edge,e.offset)}projectWorld(){let e=this.box(),s=tt(this.world,e.width,e.height,this.yaw,void 0,void 0,qe(this.world));s.cx+=e.left,s.cy+=e.top,this.camera=s;let t=new Map;for(let[n,i]of this.world){let o=Me(i,s);t.set(n,{id:n,layer:i.tier,x:o.x,y:o.y,width:i.w,height:i.h})}return t}pathFor(e,s){let t=this.positions.get(e.from)??this.targets.get(e.from),n=this.positions.get(e.to)??this.targets.get(e.to);return Pt(t,n,s)}resize(){let e=this.container.getBoundingClientRect();this.width=Math.max(e.width||this.container.clientWidth||800,200),this.height=Math.max(e.height||this.container.clientHeight||500,160),this.dpr=Math.min(globalThis.devicePixelRatio||1,2),this.canvas.width=Math.round(this.width*this.dpr),this.canvas.height=Math.round(this.height*this.dpr);let s=this.opts.morphMs;this.opts.morphMs=0,this.applyScene(this.scene,this.spec,this.matched,this.filtered),this.opts.morphMs=s}frontArrival(e){return Math.min(this.failure.hop.get(e.to)??0,this.failure.hop.get(e.from)??99)*Ve}draw(){let{ctx:e,theme:s}=this;e.save(),e.setTransform(this.dpr,0,0,this.dpr,0,0),e.clearRect(0,0,this.width,this.height),e.fillStyle=s.background,e.fillRect(0,0,this.width,this.height);let t=this.hovered?.kind==="node"?this.hovered.node.id:this.selected??void 0,n=t,i=this.hovered?.kind==="edge"?this.hovered.edge.id:void 0,o=(re()-this.killedAt)/1e3;this.isWorld()?this.drawTiers():this.drawGroups();for(let a of this.views)this.drawEdge(a,this.isDimmed(a,t,i),o);for(let a of this.views)this.drawParticles(a,this.isDimmed(a,t,i));if(this.opts.showEdgeLabels&&!this.morphing)for(let a of this.views)this.drawEdgeLabel(a);if(this.isWorld()){let a=new Map;for(let[d,c]of this.world)a.set(d,this.camera?Me(c,this.camera).depth:c.z);let l=[...this.scene.nodes].sort((d,c)=>(a.get(d.id)??0)-(a.get(c.id)??0));for(let d of l)this.drawWorldNode(d,n,o)}else for(let a of this.scene.nodes)this.drawNode(a,n,o);this.opts.showLegend&&this.drawLegend(),e.restore()}isDimmed(e,s,t){if(t!==void 0)return e.edge.id!==t;if(s!==void 0&&e.edge.from!==s&&e.edge.to!==s)return!0;if(this.highlight){let n=e.edge.dims[this.highlight.field]??e.edge.spans[this.highlight.field]?.[0],i=e.edge.spans[this.highlight.field];if(!(n===this.highlight.value||(i?.includes(this.highlight.value)??!1)))return!0}return!1}drawEdge(e,s,t){let{ctx:n,theme:i}=this,o=this.failure.edgeHealth.get(e.edge.id)??"flowing",a=this.frontArrival(e.edge),l=o==="severed"?at((t-a)/Ve):0,d=Jt.has(e.edge.kind);n.save(),n.globalAlpha=s?.2:o==="severed"?.85-l*.45:1;let c=o==="severed"?i.error:o==="strained"?i.flow.retry:d?i.edgeMuted:i.edge;n.strokeStyle=c,n.lineWidth=e.width,!s&&o==="flowing"&&(n.save(),n.strokeStyle=e.style.hue,n.lineWidth=e.width+6,n.globalAlpha=.07+Math.min(e.width/9,1)*.13,n.beginPath(),n.moveTo(e.path.a.x,e.path.a.y),n.quadraticCurveTo(e.path.ctrl.x,e.path.ctrl.y,e.path.b.x,e.path.b.y),n.stroke(),n.restore()),e.style.coupling==="rigid"?this.strokeRigid(e,c):(n.setLineDash(e.style.dash??[]),n.beginPath(),n.moveTo(e.path.a.x,e.path.a.y),n.quadraticCurveTo(e.path.ctrl.x,e.path.ctrl.y,e.path.b.x,e.path.b.y),n.stroke(),n.setLineDash([])),e.style.coupling==="breakaway"&&this.drawBreakaway(e,o,l),e.style.coupling==="rigid"&&l>0&&this.drawFracture(e,l);let h=e.path.pointAt(.985),p=e.path.tangentAt(.985),u=6.5;n.fillStyle=c,n.beginPath(),n.moveTo(h.x,h.y),n.lineTo(h.x-p.x*u-p.y*u*.5,h.y-p.y*u+p.x*u*.5),n.lineTo(h.x-p.x*u+p.y*u*.5,h.y-p.y*u-p.x*u*.5),n.closePath(),n.fill(),n.restore()}strokeRigid(e,s){let{ctx:t}=this;t.save(),t.strokeStyle=s,t.lineWidth=1.1;for(let n of[-1.9,1.9]){t.beginPath();for(let i=0;i<=20;i++){let o=i/20,a=e.path.pointAt(o),l=e.path.tangentAt(o),d=a.x-l.y*n,c=a.y+l.x*n;i===0?t.moveTo(d,c):t.lineTo(d,c)}t.stroke()}for(let n of[.24,.5,.76]){let i=e.path.pointAt(n),o=e.path.tangentAt(n);t.beginPath(),t.moveTo(i.x-o.y*3.4,i.y+o.x*3.4),t.lineTo(i.x+o.y*3.4,i.y-o.x*3.4),t.stroke()}t.restore()}drawBreakaway(e,s,t){let{ctx:n,theme:i}=this,o=s==="severed"?t:0,a=5+o*9,l=e.path.pointAt(.5),d=e.path.tangentAt(.5),c=-d.y,h=d.x;n.save(),n.strokeStyle=o>0?i.flow.retry:i.edge,n.lineWidth=1.6;for(let p of[-1,1]){let u=l.x+d.x*a*p,g=l.y+d.y*a*p;n.beginPath(),n.moveTo(u+c*4.2-d.x*3*p,g+h*4.2-d.y*3*p),n.lineTo(u,g),n.lineTo(u-c*4.2-d.x*3*p,g-h*4.2-d.y*3*p),n.stroke()}if(o>.15){let p=e.path.pointAt(.34);n.strokeStyle=i.flow.retry,n.lineWidth=2,n.globalAlpha=Math.min(1,(o-.15)*2.4),n.beginPath(),n.arc(p.x,p.y,9,Math.atan2(h,c)-1.05,Math.atan2(h,c)+1.05),n.stroke()}n.restore()}drawFracture(e,s){let{ctx:t,theme:n}=this,i=e.path.pointAt(.5),o=e.path.tangentAt(.5),a=-o.y,l=o.x,d=4+s*7;t.save(),t.strokeStyle=n.error,t.lineWidth=2,t.lineCap="round",t.beginPath(),t.moveTo(i.x+a*d,i.y+l*d),t.lineTo(i.x+o.x*d*.5-a*d*.25,i.y+o.y*d*.5-l*d*.25),t.lineTo(i.x-o.x*d*.5+a*d*.25,i.y-o.y*d*.5+l*d*.25),t.lineTo(i.x-a*d,i.y-l*d),t.stroke(),t.restore()}drawParticles(e,s=!1){if(!this.opts.animate)return;let{ctx:t,theme:n}=this,o=(this.failure.edgeHealth.get(e.edge.id)??"flowing")==="strained"?n.flow.retry:e.style.hue,a=e.style.glyph==="dot"?this.sprite(o):null;t.save();for(let l of e.stream.particles){if(l.t<0)continue;let d=e.path.pointAt(l.t),c=e.path.tangentAt(l.t),h=d.x-c.y*l.wobble,p=d.y+c.x*l.wobble,u=Math.min(1,l.t*14,(1-l.t)*14)*(s?.18:1);if(l.error)t.globalAlpha=u,t.strokeStyle=n.error,t.lineWidth=1.4,t.beginPath(),t.arc(h,p,l.radius+1.1,0,Math.PI*2),t.stroke();else if(a){let g=l.radius*2.9,m=l.speed*.04/e.path.length;for(let f=3;f>=1;f--){let v=l.t-m*f;if(v<=0)continue;let R=e.path.pointAt(v),E=e.path.tangentAt(v),b=g*(1-f*.2);t.globalAlpha=u*(e.spread>6?.9:1)*(.5-f*.13),t.drawImage(a,R.x-E.y*l.wobble-b/2,R.y+E.x*l.wobble-b/2,b,b)}t.globalAlpha=u*(e.spread>6?.9:1),t.drawImage(a,h-g/2,p-g/2,g,g)}else t.globalAlpha=u*(e.spread>6?.82:.92),hs(t,e.style.glyph,h,p,l.radius,o,c)}t.restore()}sprite(e){let s=this.sprites.get(e);if(s)return s;s=document.createElement("canvas");let t=32;s.width=t,s.height=t;let n=s.getContext("2d"),i=n.createRadialGradient(t/2,t/2,0,t/2,t/2,t/2);return i.addColorStop(0,e),i.addColorStop(.62,e),i.addColorStop(.8,Tn(e,.35)),i.addColorStop(1,Tn(e,0)),n.fillStyle=i,n.fillRect(0,0,t,t),this.sprites.set(e,s),s}drawEdgeLabel(e){if(e.labelT===null)return;let{ctx:s,theme:t}=this,n=Rn(e.edge),i=e.path.pointAt(e.labelT);s.save(),s.font="10px ui-monospace, SFMono-Regular, Menlo, monospace",s.textAlign="center",s.textBaseline="middle";let o=s.measureText(n).width+8;s.globalAlpha=.94,s.fillStyle=t.background,se(s,i.x-o/2,i.y-8,o,16,4),s.fill(),s.globalAlpha=1,s.fillStyle=Jt.has(e.edge.kind)?t.flow.retry:t.textMuted,s.fillText(n,i.x,i.y),s.restore()}drawNode(e,s,t){let n=this.positions.get(e.id);if(!n)return;let{ctx:i,theme:o}=this,a=this.styles.nodes.get(e.id)??{fill:o.node[e.kind],halo:0,haloDanger:!1,haloIntensity:0,column:null,group:null},l=this.failure.health.get(e.id)??"ok",d=(this.failure.hop.get(e.id)??0)*Ve,c=l!=="ok"?at((t-d)/Ve):0,h=this.origins.get(e.id)?.fade??1,p=this.morphing&&h===0?at((re()-this.morphFrom)/this.opts.morphMs):1,u=n.x-n.width/2,g=n.y-n.height/2;i.save(),i.globalAlpha=(s&&s!==e.id?.55:1)*p;let m=(s&&s!==e.id?.55:1)*p;if(a.haloDanger&&a.haloIntensity>0)this.drawDangerGlow(u,g,n.width,n.height,a.haloIntensity,m);else if(a.halo>0){let E=Math.min(a.halo,Math.max(2,n.height*.28));i.strokeStyle=o.border,i.lineWidth=1+a.halo/16*1.5,i.globalAlpha*=.55,se(i,u-E,g-E,n.width+E*2,n.height+E*2,12),i.stroke(),i.globalAlpha=m}if(i.fillStyle=a.fill,i.strokeStyle=s===e.id?o.highlight:o.border,i.lineWidth=s===e.id?1.8:1,se(i,u,g,n.width,n.height,8),i.fill(),i.stroke(),this.drawSelectionRing(e.id,n.x,n.y,n.width+10,n.height+10,12),this.drawShock(e.id,n.x,n.y),l==="dead"&&c>0){i.save(),se(i,u,g,n.width,n.height,8),i.clip(),i.globalAlpha=c*.9,i.fillStyle=o.background,i.fillRect(u,g,n.width,n.height),i.strokeStyle=o.error,i.lineWidth=1,i.globalAlpha=c*.55;for(let E=-n.height;E<n.width;E+=6)i.beginPath(),i.moveTo(u+E,g+n.height),i.lineTo(u+E+n.height,g),i.stroke();i.restore(),i.strokeStyle=o.error,i.lineWidth=1.6,se(i,u,g,n.width,n.height,8),i.stroke()}else l==="degraded"&&c>0&&(i.save(),i.strokeStyle=o.flow.retry,i.lineWidth=2,i.setLineDash([4,4]),i.globalAlpha=c*(.6+.4*Math.sin(t*4)),se(i,u-4,g-4,n.width+8,n.height+8,11),i.stroke(),i.restore());i.fillStyle=l==="dead"?o.textMuted:o.nodeText,i.font=Qt(12),i.textAlign="center",i.textBaseline="middle";let f=n.width-12;if(n.height<36){i.font=Qt(n.height<28?10:11),i.fillText(ze(i,e.label,f),n.x,n.y),i.restore();return}let R=bs(i,e.label,f);R.text.length>1?(i.fillText(R.text[0],n.x,n.y-8),i.fillText(R.text[1],n.x,n.y+5)):(i.fillText(R.text[0],n.x,n.y-5),i.fillStyle=o.textMuted,i.font="9px ui-monospace, SFMono-Regular, Menlo, monospace",i.fillText(ze(i,e.sublabel??e.kind,f),n.x,n.y+10)),i.restore()}drawGroups(){let{ctx:e,theme:s}=this,t=new Map;for(let n of this.scene.nodes){let i=this.styles.nodes.get(n.id)?.group,o=this.positions.get(n.id);if(!i||!o)continue;let a=t.get(i)??{minX:1/0,maxX:-1/0,minY:1/0,maxY:-1/0};a.minX=Math.min(a.minX,o.x-o.width/2),a.maxX=Math.max(a.maxX,o.x+o.width/2),a.minY=Math.min(a.minY,o.y-o.height/2),a.maxY=Math.max(a.maxY,o.y+o.height/2),t.set(i,a)}if(!(t.size<2)){e.save();for(let[n,i]of t){let a=i.minX-14,l=i.minY-14-16,d=i.maxX-i.minX+28,c=i.maxY-i.minY+28+16;e.globalAlpha=1,e.fillStyle=s.grid,e.strokeStyle=s.border,e.lineWidth=1,se(e,a,l,d,c,14),e.fill(),e.stroke(),e.fillStyle=s.textMuted,e.font="500 10px ui-monospace, SFMono-Regular, Menlo, monospace",e.textAlign="left",e.textBaseline="top",e.fillText(n.toUpperCase(),a+12,l+6)}e.restore()}}drawTiers(){if(!this.camera)return;let{ctx:e,theme:s}=this,t=new Map;for(let i of this.world.values())t.set(i.tier,i.y);let n=[...t.entries()].sort((i,o)=>i[1]-o[1]);e.save();for(let[i,o]of n){let a=qe(this.world),l=[{x:-a,y:o,z:-a},{x:a,y:o,z:-a},{x:a,y:o,z:a},{x:-a,y:o,z:a}].map(h=>Me(h,this.camera));e.beginPath(),e.moveTo(l[0].x,l[0].y);for(let h of l.slice(1))e.lineTo(h.x,h.y);e.closePath(),e.globalAlpha=.5,e.fillStyle=s.grid,e.fill(),e.globalAlpha=.85,e.strokeStyle=s.border,e.lineWidth=1,e.stroke(),e.globalAlpha=1,e.fillStyle=s.textMuted,e.font="10px ui-monospace, SFMono-Regular, Menlo, monospace",e.textAlign="left",e.textBaseline="middle";let d=this.tierLabel(i),c=l[3];e.fillText(d,c.x+6,c.y-8)}e.restore()}tierLabel(e){let s=this.spec.world?.tierBy;return s?(this.spec.world?.tiers??[...new Set(this.scene.nodes.map(n=>ot(n,s)??"\u2205"))].sort())[e]??`${s} ${e}`:Et[e]??`tier ${e}`}drawWorldNode(e,s,t){let n=this.positions.get(e.id);if(!n)return;let{ctx:i,theme:o}=this,a=this.styles.nodes.get(e.id)??{fill:o.node[e.kind],halo:0,haloDanger:!1,haloIntensity:0,column:null,group:null},l=this.failure.health.get(e.id)??"ok",d=(this.failure.hop.get(e.id)??0)*Ve,c=l!=="ok"?at((t-d)/Ve):0,h=this.spec.world?.gauges??["cpu","mem"],p=this.spec.world?.instances??"instances",u={...yn(o),instances:Math.max(1,e.attrs[p]??1),gauges:h.filter(f=>e.attrs[f]!==void 0).map(f=>({label:f,value:e.attrs[f]})),fill:a.fill,health:l,struck:c},g=ue[Rt(e,this.componentHint(e))]??ue["service-rack"],m={cx:n.x,top:n.y-n.height/2,w:n.width,h:n.height};if(i.save(),i.globalAlpha=s&&s!==e.id?.45:1,i.globalAlpha*=.5,i.fillStyle=o.background,i.beginPath(),i.ellipse(n.x,m.top+m.h+2,n.width*.42,4,0,0,Math.PI*2),i.fill(),i.globalAlpha=s&&s!==e.id?.45:1,g.draw(i,m,u),this.drawSelectionRing(e.id,n.x,m.top+m.h*.5,n.width+16,m.h+16,14),this.drawShock(e.id,n.x,m.top+m.h*.6),l==="dead"&&c>0){i.save(),i.globalAlpha=c*.6,i.strokeStyle=o.error,i.lineWidth=1.4;for(let f=-n.height;f<n.width;f+=6)i.beginPath(),i.moveTo(n.x-n.width/2+f,m.top+m.h),i.lineTo(n.x-n.width/2+f+m.h,m.top),i.stroke();i.restore()}else l==="degraded"&&c>0&&(i.save(),i.globalAlpha=c*(.55+.45*Math.sin(t*3)),i.strokeStyle=o.flow.retry,i.lineWidth=2,i.setLineDash([4,4]),i.beginPath(),i.ellipse(n.x,m.top+m.h*.55,n.width*.62,m.h*.66,0,0,Math.PI*2),i.stroke(),i.restore());if(i.globalAlpha=s&&s!==e.id?.45:1,i.textAlign="center",i.textBaseline="top",i.fillStyle=l==="dead"?o.textMuted:o.nodeText,i.font="600 11px ui-sans-serif, system-ui, -apple-system, Segoe UI, sans-serif",i.fillText(ze(i,e.label,n.width*1.55),n.x,m.top+m.h+6),e.sublabel||u.instances>1){i.fillStyle=o.textMuted,i.font="9px ui-monospace, SFMono-Regular, Menlo, monospace";let f=[e.sublabel,u.instances>1?`\xD7${Math.round(u.instances)}`:null].filter(Boolean).join(" \xB7 ");i.fillText(ze(i,f,n.width*1.55),n.x,m.top+m.h+18)}i.restore()}componentHint(e){let s=this.spec.world?.componentBy;if(e.component)return e.component;if(!s)return;let t=ot(e,s);if(t)return this.spec.world?.componentMap?.[t]??t}drawDangerGlow(e,s,t,n,i,o){let{ctx:a,theme:l}=this,d=at(i),c=d>.5?(.5+.5*Math.sin(re()/540+e*.013+s*.007))*(d-.5)*2:0,h=l.name==="dark",p=3+d*d*26+c*5,u=(.18+d*.62+c*.12)*o;a.save(),a.fillStyle=l.error;for(let[m,f]of[[1,.22],[.6,.3],[.3,.42]]){a.shadowColor=l.error,a.shadowBlur=p*m*2.2,a.globalAlpha=u*f*(h?1:.8);let v=p*m*.5;se(a,e-v,s-v,t+v*2,n+v*2,10+v),a.fill()}a.shadowBlur=0,a.strokeStyle=l.error,a.lineWidth=1+d*d*3.5,a.globalAlpha=(.4+d*.55)*o;let g=2+d*d*5;se(a,e-g,s-g,t+g*2,n+g*2,10),a.stroke(),a.restore()}drawSelectionRing(e,s,t,n,i,o){if(this.selected!==e)return;let{ctx:a,theme:l}=this,d=.5+.5*Math.sin(re()/700);a.save(),a.strokeStyle=l.highlight,a.lineWidth=1.5,a.globalAlpha=.9,se(a,s-n/2,t-i/2,n,i,o),a.stroke(),a.globalAlpha=.18+d*.14,a.lineWidth=6,se(a,s-n/2-3,t-i/2-3,n+6,i+6,o+3),a.stroke(),a.restore()}drawShock(e,s,t){let{ctx:n,theme:i}=this;for(let o of this.shocks){if(o.id!==e)continue;let a=(re()-o.at)/1600,l=12+a*150;n.save(),n.strokeStyle=i.error,n.lineWidth=2.2*(1-a),n.globalAlpha=(1-a)*.8,n.beginPath(),n.ellipse(s,t,l,l*(this.isWorld()?.42:1),0,0,Math.PI*2),n.stroke(),n.restore()}}drawLegend(){let{ctx:e,theme:s}=this,n=(this.styles.legend.length?this.styles.legend.map(d=>`${d.channel} = ${d.field}`):["density = rps (log)","speed = 1 / latency","ring = error"]).join(" \xB7 ");e.save(),e.font="10px ui-monospace, SFMono-Regular, Menlo, monospace";let i=Math.min(e.measureText(n).width+20,this.width-20),o=22,a=Math.max(10,(this.width-i)/2),l=this.height-o-9;e.globalAlpha=.95,e.fillStyle=s.surface,e.strokeStyle=s.border,e.lineWidth=1,se(e,a,l,i,o,11),e.fill(),e.stroke(),e.globalAlpha=1,e.fillStyle=s.textMuted,e.textAlign="center",e.textBaseline="middle",e.fillText(ze(e,n,i-16),a+i/2,l+o/2),e.restore()}placeLabels(){let{ctx:e}=this;e.save(),e.font="10px ui-monospace, SFMono-Regular, Menlo, monospace";let s=[];for(let i of this.positions.values())s.push({x:i.x-i.width/2-3,y:i.y-i.height/2-3,w:i.width+6,h:i.height+6});let t=[.5,.4,.6,.32,.68,.24,.76,.18,.82],n=[...this.views].sort((i,o)=>o.edge.metrics.rps-i.edge.metrics.rps);for(let i of n){let o=Rn(i.edge),a=e.measureText(o).width+10,l=17,d=null;for(let c of t){let h=i.path.pointAt(c),p={x:h.x-a/2,y:h.y-l/2,w:a,h:l};if(!s.some(u=>fs(u,p))){d=c,s.push(p);break}}i.labelT=d}e.restore()}hitTest(e,s){for(let n of this.scene.nodes){let i=this.positions.get(n.id);if(i&&e>=i.x-i.width/2&&e<=i.x+i.width/2&&s>=i.y-i.height/2&&s<=i.y+i.height/2)return{kind:"node",node:n}}let t=null;for(let n of this.views){let i=At(n.path,{x:e,y:s});i<9&&(!t||i<t.d)&&(t={v:n,d:i})}return t?{kind:"edge",edge:t.v.edge}:null}updateTooltip(){let e=this.hovered;if(!e||!this.pointer){this.tooltip.style.opacity="0";return}this.tooltip.style.background=this.theme.tooltipBg,this.tooltip.style.color=this.theme.tooltipText,this.tooltip.textContent=e.kind==="node"?gs(e.node,this.failure.health.get(e.node.id)??"ok"):ys(e.edge,this.styles.edges.get(e.edge.id)),this.tooltip.style.opacity="1";let s=Math.min(this.pointer.x+14,this.width-this.tooltip.offsetWidth-8),t=Math.min(this.pointer.y+14,this.height-this.tooltip.offsetHeight-8);this.tooltip.style.left=`${Math.max(8,s)}px`,this.tooltip.style.top=`${Math.max(8,t)}px`}};function hs(r,e,s,t,n,i,o){switch(r.fillStyle=i,r.strokeStyle=i,e){case"ring":r.lineWidth=1.3,r.beginPath(),r.arc(s,t,n+.6,0,Math.PI*2),r.stroke();return;case"square":r.fillRect(s-n,t-n,n*2,n*2);return;case"triangle":r.beginPath(),r.moveTo(s+o.x*n*1.5,t+o.y*n*1.5),r.lineTo(s-o.x*n-o.y*n,t-o.y*n+o.x*n),r.lineTo(s-o.x*n+o.y*n,t-o.y*n-o.x*n),r.closePath(),r.fill();return;case"bar":r.lineWidth=n*1.6,r.beginPath(),r.moveTo(s-o.x*n*1.4,t-o.y*n*1.4),r.lineTo(s+o.x*n*1.4,t+o.y*n*1.4),r.stroke();return;case"diamond":r.beginPath(),r.moveTo(s,t-n*1.35),r.lineTo(s+n*1.35,t),r.lineTo(s,t+n*1.35),r.lineTo(s-n*1.35,t),r.closePath(),r.fill();return;default:r.beginPath(),r.arc(s,t,n,0,Math.PI*2),r.fill()}}function us(r,e){let s=new Map;if(!r.nodes.length)return s;let t=r.nodeKey.filter(n=>e.nodeKey.includes(n));for(let n of e.nodes){if(r.nodes.some(o=>o.id===n.id)){s.set(n.id,[n.id]);continue}if(!t.length)continue;let i=r.nodes.filter(o=>t.every(a=>o.key[a]===n.key[a])).map(o=>o.id);i.length&&s.set(n.id,i)}return s}function ps(r,e,s){let t=new Map;if(!s.length)return t;let n=new Map(s.map(l=>[l.edge.id,l.stream])),i=r.nodeKey.filter(l=>e.nodeKey.includes(l)),o=new Map;for(let l of r.nodes){let d=e.nodes.find(c=>c.id===l.id||i.length>0&&i.every(h=>c.key[h]===l.key[h]));d&&o.set(l.id,d.id)}let a=new Set;for(let l of e.edges){if(n.has(l.id)&&!a.has(l.id)){t.set(l.id,n.get(l.id)),a.add(l.id);continue}let d=r.edges.find(c=>!a.has(c.id)&&o.get(c.from)===l.from&&o.get(c.to)===l.to&&n.has(c.id));d&&(t.set(l.id,n.get(d.id)),a.add(d.id))}return t}function Sn(r,e,s){let t=new Map;for(let[n,i]of e){let o=r.get(n);if(!o){t.set(n,i);continue}t.set(n,{...i,x:o.x+(i.x-o.x)*s,y:o.y+(i.y-o.y)*s})}return t}function ms(r){return{id:r.id,metrics:r.metrics}}function Rn(r){return`${Z(r.metrics.rps)}/s \xB7 ${Pe(r.metrics.latencyMs)}`}function fs(r,e){return r.x<e.x+e.w&&e.x<r.x+r.w&&r.y<e.y+e.h&&e.y<r.y+r.h}function ot(r,e){return r.key[e]??(r.spans[e]?.length===1?r.spans[e][0]:void 0)}function En(r){return r?r.kind==="node"?`n:${r.node.id}`:`e:${r.edge.id}`:null}function gs(r,e){let s=[r.sublabel?`${r.sublabel} \xB7 ${r.label}`:r.label];for(let[n,i]of Object.entries(r.key))s.push(`${te(n)}${i}`);s.push(`${te("inbound")}${Z(r.inboundRps)}/s`),s.push(`${te("outbound")}${Z(r.outboundRps)}/s`),e!=="ok"&&s.push(`${te("state")}${e.toUpperCase()}`);let t=Object.entries(r.spans).filter(([,n])=>n.length&&n.length<=6);for(let[n,i]of t.slice(0,4))s.push(`${te(n)}${i.join(", ")}`);return r.records>1&&s.push(`${te("records")}${r.records}`),s.join(`
|
|
4
|
+
`)}function ys(r,e){let s=r.metrics,t=[`${r.from} \u2192 ${r.to}`];r.label&&t.push(r.label);for(let[i,o]of Object.entries(r.dims))t.push(`${te(i)}${o}`);t.push(`${te("rps")}${Z(s.rps)}/s`),t.push(`${te("latency")}${Pe(s.latencyMs)}`),t.push(`${te("errors")}${(s.errorRate*100).toFixed(s.errorRate<.01?2:1)}%`),t.push(`${te("payload")}${bt(s.bytes)}`),e&&e.coupling!=="plain"&&t.push(`${te("coupling")}${e.coupling}`);let n=Object.entries(r.spans).filter(([,i])=>i.length>1&&i.length<=6);for(let[i,o]of n.slice(0,3))t.push(`${te(i)}${o.join(", ")}`);return r.records>1&&t.push(`${te("records")}${r.records}`),t.join(`
|
|
5
|
+
`)}function te(r){return`${r.padEnd(10)}`}function se(r,e,s,t,n,i){let o=Math.min(i,t/2,n/2);r.beginPath(),r.moveTo(e+o,s),r.arcTo(e+t,s,e+t,s+n,o),r.arcTo(e+t,s+n,e,s+n,o),r.arcTo(e,s+n,e,s,o),r.arcTo(e,s,e+t,s,o),r.closePath()}var Qt=r=>`600 ${r}px ui-sans-serif, system-ui, -apple-system, Segoe UI, sans-serif`;function bs(r,e,s){if(r.measureText(e).width<=s)return{text:[e]};let t=[...e.matchAll(/[-_/. ]/g)].map(i=>i.index??0),n=e.length/2;for(let i of t.sort((o,a)=>Math.abs(o-n)-Math.abs(a-n))){let o=e.slice(0,i+1),a=e.slice(i+1);if(r.measureText(o).width<=s&&r.measureText(a).width<=s)return{text:[o,a]}}for(let i of[11,10,9,8])if(r.font=Qt(i),r.measureText(e).width<=s)return{text:[e]};return{text:[ze(r,e,s)]}}function ze(r,e,s){if(r.measureText(e).width<=s)return e;let t=e;for(;t.length>1&&r.measureText(`${t}\u2026`).width>s;)t=t.slice(0,-1);return`${t}\u2026`}var ws=3;function vs(r,e){let s=new Map;for(let n of r){let i=n.from<n.to?`${n.from}|${n.to}`:`${n.to}|${n.from}`,o=n.from<n.to?0:1,a=s.get(i);a||s.set(i,a=[[],[]]),a[o].push(n)}let t=new Map;for(let[,n]of s){let i=n[0].length>0&&n[1].length>0;for(let o of n){let a=[...o].sort((p,u)=>(e.get(p.id)?.lane??0)-(e.get(u.id)?.lane??0)),l=a.length,d=l>1&&l<=ws,c=d?3.2:19,h=d?11:4.2;a.forEach((p,u)=>{let g=(u-(l-1)/2)*c;t.set(p.id,{offset:i?12+Math.abs(g)+(d?0:u*4):g,spread:h,phase:l>1?u/l:0})})}}return t}function Tn(r,e){if(r.startsWith("#")){let t=r.slice(1),n=t.length===3?t.split("").map(o=>o+o).join(""):t,i=Number.parseInt(n,16);return`rgba(${i>>16&255}, ${i>>8&255}, ${i&255}, ${e})`}let s=r.match(/rgba?\(([^)]+)\)/);if(s){let[t,n,i]=s[1].split(",").map(o=>o.trim());return`rgba(${t}, ${n}, ${i}, ${e})`}return r}function xs(r,e,s){return r<e?e:r>s?s:r}function at(r){return r<0?0:r>1?1:r}function Ms(r){return 1-(1-r)**3}function lt(){return typeof matchMedia=="function"&&matchMedia("(prefers-reduced-motion: reduce)").matches}function re(){return typeof performance<"u"?performance.now():Date.now()}async function Pn(r,e){let s=Zt(r,e.schema,e.current),t;try{t=await e.complete(s)}catch(a){return{spec:dt(r,e.schema,e.current),issues:[`model call failed (${a.message}); used the built-in heuristic instead`],source:"fallback"}}let n=tn(t);if(!n)return{spec:dt(r,e.schema,e.current),issues:["no JSON object found in the model response; used the built-in heuristic instead"],source:"fallback",raw:t};let{spec:i,issues:o}=en(n,e.schema);return i.nodeKey.length?{spec:i,issues:o,source:"model",raw:t}:{spec:dt(r,e.schema,e.current),issues:[...o,"proposal had no usable node key"],source:"fallback",raw:t}}function Zt(r,e,s){let t=e.dimensions.map(a=>{let l=a.values.slice(0,6).join(", "),d=[a.endpoint?"endpoint":null,a.flow?"flow":null].filter(Boolean).join("+");return`- ${a.name} (${a.cardinality} values, ${d}): ${l}${a.truncated?", \u2026":""}`}).join(`
|
|
6
|
+
`),n=Object.keys(Q).map(a=>{let l=Q[a],d=l.maxCardinality?`, max ${l.maxCardinality} values`:"";return`- ${a} (${l.target}, takes a ${l.accepts}${d}): ${l.summary}`}).join(`
|
|
7
|
+
`),i=["You configure a service-topology visualization by returning a JSON view spec.","You never write code and never invent field names. Every field you use must appear in the schema below, and every channel must be one of the listed channels.","","Rules that matter:","- nodeKey decides what a box is. Fewer dimensions means fewer, bigger boxes.","- Binding a dimension to an edge channel splits edges by it, so only bind dimensions worth splitting on.","- Never bind a high-cardinality dimension to hue, glyph, dash or lane; respect the stated maximums.","- Prefer two or three channels. A picture encoding eight things communicates none of them.",'- Use mode "world" when the request is about seeing the system as a place, or mentions instances, capacity, CPU, memory, or shapes. Otherwise use "flat".',`- distance is a real channel: bind "share" (the fraction of a caller's traffic on an edge) so coupled services sit together, "rps" for raw volume, or a dimension so things sharing it cluster. Add "strength" (0-3) to dial it.`,'- column arranges boxes left to right by a dimension; add "order" (a list of its values) to choose the sequence. halo can size by instances.',"",`Available world components: ${Object.keys(ue).join(", ")}.`,"","Reply with a single JSON object and nothing else."].join(`
|
|
8
|
+
`),o=[`Request: ${r}`,"","Dimensions:",t||"(none)","",`Measures on flows: ${e.measures.join(", ")||"(none)"}`,`Numeric attributes on things: ${e.nodeAttrs.join(", ")||"(none)"}`,"","Channels:",n,"",s?`Current spec (change as little as needed):
|
|
9
|
+
${JSON.stringify(s,null,2)}`:"","","Shape of the answer:",JSON.stringify({nodeKey:["service"],where:{env:["prod"]},channels:{hue:{field:"region"},coupling:{field:"failure"},distance:{field:"share"}},mode:"world",world:{tierBy:"kind",instances:"instances",gauges:["cpu","mem"]},title:"one short sentence describing what this view shows"},null,2)].filter(Boolean).join(`
|
|
10
|
+
`);return{system:i,user:o}}function en(r,e){let s=[],t=r??{},n=new Map(e.dimensions.map(f=>[f.name,f])),i=f=>n.has(f)||e.measures.includes(f)||e.nodeAttrs.includes(f),o=[];for(let f of Le(t.nodeKey)){let v=n.get(f);v?v.endpoint?o.push(f):s.push(`nodeKey: "${f}" is a flow dimension and cannot identify a node`):s.push(`nodeKey: no dimension called "${f}"`)}let a={};for(let[f,v]of Object.entries(fe(t.where))){let R=n.get(f);if(!R){s.push(`where: no dimension called "${f}"`);continue}let E=Le(v).filter(b=>{let k=R.truncated?!0:R.values.includes(b);return k||s.push(`where.${f}: "${b}" is not a value of that dimension`),k});E.length&&(a[f]=E)}let l={};for(let[f,v]of Object.entries(fe(t.channels))){let R=Q[f];if(!R){s.push(`channels: "${f}" is not a channel`);continue}let E=fe(v),b=typeof E.field=="string"?E.field:typeof v=="string"?v:"";if(!b||!i(b)){s.push(`channels.${f}: no field called "${b||"(missing)"}"`);continue}let k=ye(b)||e.nodeAttrs.includes(b);if(R.accepts==="measure"&&!k){s.push(`channels.${f}: needs a measure, but "${b}" is a dimension`);continue}if(R.accepts==="dimension"&&k){s.push(`channels.${f}: needs a dimension, but "${b}" is a measure`);continue}let w=n.get(b);if(R.maxCardinality&&w&&w.cardinality>R.maxCardinality){s.push(`channels.${f}: "${b}" has ${w.cardinality} values, more than ${f} can show (${R.maxCardinality})`);continue}let A={field:b};(E.scale==="log"||E.scale==="linear"||E.scale==="sqrt")&&(A.scale=E.scale),E.invert===!0&&(A.invert=!0),typeof E.strength=="number"&&Number.isFinite(E.strength)&&(A.strength=Math.max(0,Math.min(3,E.strength)));let L=Le(E.order);L.length&&(A.order=L);let N=fe(E.map);Object.keys(N).length&&(A.map=Object.fromEntries(Object.entries(N).filter(([,y])=>typeof y=="string"))),l[f]=A}let d={nodeKey:o};Object.keys(a).length&&(d.where=a),Object.keys(l).length&&(d.channels=l),(t.mode==="world"||t.mode==="flat")&&(d.mode=t.mode);let c=fe(t.layout);(c.rowSort==="auto"||c.rowSort==="name"||c.rowSort==="traffic")&&(d.layout={rowSort:c.rowSort}),typeof t.title=="string"&&(d.title=t.title.slice(0,160)),t.dropSelfEdges===!0&&(d.dropSelfEdges=!0);let h=Le(t.killed);h.length&&(d.killed=h);let p=fe(t.focus),u=Le(p.expandBy).filter(f=>n.get(f)?.endpoint),g=Object.fromEntries(Object.entries(fe(p.match)).filter(([f,v])=>n.has(f)&&typeof v=="string"));u.length&&Object.keys(g).length&&(d.focus={match:g,expandBy:u});let m=fe(t.world);if(Object.keys(m).length){let f={};typeof m.tierBy=="string"&&n.has(m.tierBy)?f.tierBy=m.tierBy:m.tierBy&&s.push(`world.tierBy: no dimension called "${String(m.tierBy)}"`);let v=Le(m.tiers);v.length&&(f.tiers=v),typeof m.componentBy=="string"&&n.has(m.componentBy)&&(f.componentBy=m.componentBy);let R=fe(m.componentMap),E={};for(let[k,w]of Object.entries(R))typeof w=="string"&&ue[w]?E[k]=w:s.push(`world.componentMap.${k}: "${String(w)}" is not a registered component`);Object.keys(E).length&&(f.componentMap=E),typeof m.instances=="string"&&e.nodeAttrs.includes(m.instances)&&(f.instances=m.instances);let b=Le(m.gauges).filter(k=>e.nodeAttrs.includes(k));b.length&&(f.gauges=b.slice(0,4)),Object.keys(f).length&&(d.world=f)}return{spec:d,issues:s}}function dt(r,e,s){let t=r.toLowerCase(),n=h=>e.dimensions.some(p=>p.name===h&&p.endpoint),i=h=>e.dimensions.some(p=>p.name===h),o=[];n("region")&&t.includes("region")&&o.push("region"),n("service")&&o.push("service"),n("api")&&/\bapi|endpoint|route/.test(t)&&o.push("api"),o.length||o.push(e.dimensions.find(h=>h.endpoint)?.name??"service");let a={};i("failure")&&/fail|blast|outage|kill|resilien|depend/.test(t)&&(a.coupling={field:"failure"}),i("region")&&!o.includes("region")&&/region|geo|locality/.test(t)?a.hue={field:"region"}:i("env")&&/env|prod|staging/.test(t)&&(a.hue={field:"env"}),/cluster|close|near|coupl|distance/.test(t)&&(a.distance=i("team")&&/team|owner/.test(t)?{field:"team"}:{field:/volume|rps|heavy/.test(t)?"rps":"share"}),/error|failing|5xx/.test(t)&&(a.radius={field:"errorRate",scale:"sqrt"}),/slow|latency|p99/.test(t)&&(a.speed={field:"latencyMs"});let l={nodeKey:o};return Object.keys(a).length&&(l.channels=a),/world|3d|instance|capacity|cpu|memory|shape|machine|fleet/.test(t)?(l.mode="world",l.world={...e.nodeAttrs.includes("instances")?{instances:"instances"}:{},gauges:["cpu","mem"].filter(h=>e.nodeAttrs.includes(h))}):s?.mode&&(l.mode=s.mode,s.world&&(l.world=s.world)),e.dimensions.find(h=>h.name==="env")?.values.includes("prod")&&/prod/.test(t)&&(l.where={env:["prod"]}),l.title=r.slice(0,160),l}function tn(r){let s=r.match(/```(?:json)?\s*([\s\S]*?)```/)?.[1]??r,t=s.indexOf("{");if(t<0)return null;let n=0,i=!1,o=!1;for(let a=t;a<s.length;a++){let l=s[a];if(i){o?o=!1:l==="\\"?o=!0:l==='"'&&(i=!1);continue}if(l==='"')i=!0;else if(l==="{")n++;else if(l==="}"&&--n===0)try{return JSON.parse(s.slice(t,a+1))}catch{return null}}return null}function fe(r){return r&&typeof r=="object"&&!Array.isArray(r)?r:{}}function Le(r){return typeof r=="string"?[r]:Array.isArray(r)?r.filter(e=>typeof e=="string"):[]}var ks=[["orders","Payments",["POST /orders","GET /orders/{id}","POST /orders/{id}/cancel"]],["pricing","Payments",["GET /quote","POST /quote/bulk"]],["catalog","Identity",["GET /items/{sku}","GET /items:search","POST /items"]],["identity","Identity",["POST /token","GET /me","POST /introspect"]],["payments","Payments",["POST /charges","POST /refunds","GET /charges/{id}"]],["shipping","Payments",["POST /shipments","GET /rates"]],["inventory","Risk",["GET /stock/{sku}","POST /reserve"]],["fraud","Risk",["POST /score"]],["notify","Identity",["POST /send","GET /prefs"]],["reviews","Risk",["GET /reviews/{sku}","POST /reviews"]],["ledger","Payments",["POST /entries","GET /balance"]],["search","Identity",["GET /search","POST /index"]]];function Ss(r){switch(r){case"redis":case"dynamo":return"get";case"kafka":return"produce";case"s3":return"put";default:return"query"}}var Ft=[["orders-db","postgres"],["pricing-cache","redis"],["catalog-db","postgres"],["identity-db","postgres"],["payments-db","postgres"],["shipping-db","postgres"],["inventory-kv","dynamo"],["fraud-features","cassandra"],["notify-queue","kafka"],["reviews-blob","s3"],["ledger-db","postgres"],["search-index","clickhouse"]];function On(r={}){let{seed:e=21,services:s=12,envs:t=["prod","staging","dev"],regions:n=["eu-west","us-east"],baseRps:i=6400,title:o="Messy mesh: 12 services, ~40 APIs, 3 environments, 2 regions"}=r,a=ee(e),l=(R,E)=>R+a()*(E-R),d=ks.slice(0,s).map(([R,E,b],k)=>({name:R,team:E,apis:b,store:{name:Ft[k%Ft.length][0],engine:Ft[k%Ft.length][1]}})),c=[];for(let R of t)for(let E of n){if(R!=="prod"&&E!==n[0])continue;let b=R==="prod"?E===n[0]?1:.62:R==="staging"?.06:.015;c.push({env:R,region:E,share:b})}let h=[],p=[],u=d.filter(R=>!["identity","orders","payments"].includes(R.name)),g=u[Math.floor(a()*u.length)].name,m=u[Math.floor(a()*u.length)];m.name===g&&(m=u[(u.indexOf(m)+1)%u.length]);let f=m.apis[0],v=(R,E,b,k,w,A,L)=>{h.push({from:R,to:E,dims:b,metrics:{rps:Math.round(k*100)/100,latencyMs:Math.round(w*10)/10,errorRate:Math.round(A*1e4)/1e4,bytes:Math.round(L)}})};for(let{env:R,region:E,share:b}of c){let k=i*b;v({service:"clients",api:"browser",kind:"client"},{service:"gateway",api:"ingress",kind:"gateway"},{env:R,region:E,failure:"fail-closed",protocol:"https"},k,l(1.5,3),l(0,.002),820);let w=d.flatMap(x=>x.apis.slice(0,2).map(P=>({service:x.name,api:P}))),A=w.map(()=>Math.exp(l(-2.6,2.6))),L=A.reduce((x,P)=>x+P,0),N=new Set(["identity","orders","payments"]),y=(x,P,C)=>R!=="prod"?C:x===m.name&&P===f?l(.12,.3):x===g?l(.02,.05):C;w.forEach((x,P)=>{v({service:"gateway",api:"ingress",kind:"gateway"},{...x,kind:"service"},{env:R,region:E,failure:N.has(x.service)?"fail-closed":"fail-open",protocol:"https"},k*A[P]/L,l(4,40),y(x.service,x.api,l(0,.004)),900)});for(let x of d)for(let P of x.apis){let C=h.find(F=>F.to.service===x.name&&F.to.api===P&&F.dims?.env===R&&F.dims?.region===E)?.metrics.rps??k*Math.exp(l(-6,-2.5));v({service:x.name,api:P,kind:"service"},{service:x.store.name,api:Ss(x.store.engine),kind:An(x.store.engine),engine:x.store.engine},{env:R,region:E,failure:"fail-closed",protocol:Es(x.store.engine)},C*l(.9,2.4),Ts(x.store.engine,l(.6,1.4)),l(0,.003),l(300,5200));let T=a()<.55?1:a()<.8?2:0;for(let F=0;F<T;F++){let I=d[Math.floor(a()*d.length)];if(I.name===x.name)continue;let H=I.apis[Math.floor(a()*I.apis.length)],G=a()<.66;v({service:x.name,api:P,kind:"service"},{service:I.name,api:H,kind:"service"},{env:R,region:E,failure:G?"fail-open":"fail-closed",protocol:a()<.7?"grpc":"https"},C*l(.1,.9),l(3,70),y(I.name,H,G?l(0,.008):l(0,.004)),l(200,2600))}}for(let x of d){let P=R==="prod"?Math.round(l(4,48)):R==="staging"?2:1;p.push({key:{service:x.name},dims:{env:R,region:E,team:x.team,kind:"service"},attrs:{instances:P,cpu:Ke(l(.18,R==="prod"?.94:.4)),mem:Ke(l(.3,R==="prod"?.88:.5))}}),p.push({key:{service:x.store.name},dims:{env:R,region:E,engine:x.store.engine,team:x.team,kind:An(x.store.engine)},component:Rs(x.store.engine),attrs:{instances:R==="prod"?Math.round(l(3,12)):1,cpu:Ke(l(.25,.9)),mem:Ke(l(.4,.95))}})}p.push({key:{service:"gateway"},dims:{env:R,region:E,team:"Identity",kind:"gateway"},attrs:{instances:R==="prod"?24:2,cpu:Ke(l(.3,.8)),mem:Ke(l(.3,.7))}}),p.push({key:{service:"clients"},dims:{env:R,region:E,team:"Outside",kind:"client"},attrs:{instances:1,cpu:0,mem:0}})}return{version:1,records:h,nodes:p,meta:{title:o,window:"synthetic",note:"Every dimension here is a knob: service, api, env, region, team, engine, protocol, failure."}}}function An(r){return/redis|memcache/.test(r)?"cache":/kafka|sqs|rabbit/.test(r)?"queue":"datastore"}function Rs(r){return/postgres|mysql|aurora/.test(r)?"db-cylinder":/cassandra|clickhouse|bigtable/.test(r)?"db-discs":/dynamo|rocks|kv/.test(r)?"db-cube":/kafka|pulsar|kinesis/.test(r)?"db-drum":/redis|memcache/.test(r)?"cache-chip":/s3|gcs|blob/.test(r)?"blob-bucket":"db-cylinder"}function Es(r){return/redis/.test(r)?"resp":/kafka/.test(r)?"kafka":/s3/.test(r)?"https":"sql"}function Ts(r,e){return(/redis|memcache/.test(r)?.7:/dynamo|kv/.test(r)?4:/kafka/.test(r)?2.5:/s3|blob/.test(r)?38:/clickhouse|cassandra/.test(r)?22:9)*e}function Ke(r){return Math.round(Math.max(0,Math.min(1,r))*100)/100}var Dt=[["identity",["auth","accounts","sessions","consent"]],["payments",["ledger","checkout","refunds","payouts","fraud-signals"]],["risk",["scoring","rules","review","chargebacks"]],["marketplace",["catalog","listings","sellers","reviews","media"]],["orders",["orders-core","cart","fulfilment","returns"]],["logistics",["dispatch","routing","tracking","carriers","warehouse"]],["comms",["notify","email","push","templates"]],["search",["query","indexing","ranking","suggest"]],["pricing",["quotes","promotions","tax","fx"]],["growth",["experiments","campaigns","referrals","loyalty"]],["platform",["config","flags","observability","secrets","scheduler","api-gateway"]],["data",["ingest","warehouse","features","ml-serving","lineage"]]],Ps={edge:["bff","api","gateway","graphql","webhooks"],app:["service","orchestrator","handler","router","worker","sync","dispatcher","projector"],core:["engine","ledger","matcher","scorer","rules","calculator","validator","resolver","state"],data:["db","store","index","cache","kv","blob","timeseries"]},As={db:"postgres",store:"cassandra",index:"elasticsearch",cache:"redis",kv:"dynamo",blob:"s3",timeseries:"clickhouse"};function Ln(r={}){let{seed:e=7,services:s=600,topics:t=80,regions:n=["eu-west","us-east"],baseRps:i=42e3,title:o=`Estate: ${s} services, ${t} topics, ${Dt.length} domains`}=r,a=ee(e),l=(M,$)=>M+a()*($-M),d=M=>M[Math.floor(a()*M.length)],c=(M,$)=>{let S=Math.max(1e-9,a()),O=a(),D=Math.sqrt(-2*Math.log(S))*Math.cos(2*Math.PI*O);return Math.exp(M+$*D)},h=[],p=new Set,u=M=>{let $=M,S=2;for(;p.has($);)$=`${M}-${S++}`;return p.add($),$},g=Dt.flatMap(([M,$])=>$.map(S=>({domain:M,team:S}))),m=g.map(()=>c(0,.6)),f=m.reduce((M,$)=>M+$,0),v=s;g.forEach((M,$)=>{let S=$===g.length-1?v:Math.max(3,Math.round(s*m[$]/f)),O=Math.min(S,v);v-=O;for(let D=0;D<O;D++){let W=a(),j=D===0?"edge":W<.36?"app":W<.74?"core":"data",z=d(Ps[j]),de=u(`${M.team}-${z}`),Y=j==="data"?z==="cache"?"cache":"datastore":j==="edge"&&z==="gateway"?"gateway":"service",U=j==="edge"?0:j==="app"?1+Math.floor(a()*2):j==="core"?3+Math.floor(a()*3):6;h.push({name:de,domain:M.domain,team:M.team,tier:j,kind:Y,role:z,...j==="data"?{engine:As[z]??"postgres"}:{},level:U,inbound:0,index:0})}}),h.sort((M,$)=>M.level-$.level||a()-.5),h.forEach((M,$)=>M.index=$);let R=new Map(h.map(M=>[M.name,M])),E=new Map,b=new Map;for(let M of h)E.set(M.team,[...E.get(M.team)??[],M]),b.set(M.domain,[...b.get(M.domain)??[],M]);let w=b.get("platform").filter(M=>M.tier!=="data").filter(M=>/config|flags|secrets|auth/.test(M.name)).concat(E.get("auth").filter(M=>M.tier!=="data")),A=h.filter(M=>M.tier!=="data"&&M.tier!=="edge"),L=new Set;for(;L.size<6;)L.add(d(A).name);let N=new Set;for(;N.size<2;){let M=d(A).name;L.has(M)||N.add(M)}let y=(M,$)=>N.has(M)?l(.12,.3):L.has(M)?l(.02,.05):$,x=M=>M==="edge"?l(5,20):M==="app"?l(8,60):M==="core"?l(4,40):l(1,8),P=[],C=new Map,T=(M,$,S,O=!1)=>{if(M===$)return;let D=`${M.name}>${$.name}`;if(C.has(D))return;C.set(D,1);let W=M.team===$.team,j=$.tier==="data"?"fail-closed":W?a()<.6?"fail-closed":"fail-open":a()<.3?"fail-closed":"fail-open",z=$.tier==="data"?$.role==="cache"?"get":$.role==="blob"?"put":"query":d(["get","list","create","update","search","resolve","evaluate"]);P.push({from:M,to:$,api:z,rps:S,failure:j,protocol:$.tier==="data"?$.engine:a()<.7?"grpc":"http",back:O}),$.inbound+=S},F={name:"clients",domain:"outside",team:"outside",tier:"edge",kind:"client",role:"client",level:-1,inbound:0,index:-1},I=new Map(Dt.map(([M])=>[M,c(0,.9)])),H=[...I.values()].reduce((M,$)=>M+$,0);for(let M of h){if(M.tier!=="edge")continue;let $=E.get(M.team).filter(O=>O.tier==="edge").length,S=I.get(M.domain)/H/Dt.find(([O])=>O===M.domain)[1].length/$;T(F,M,i*S*l(.6,1.4))}for(let M of h){if(M.tier==="data")continue;M.inbound===0&&(M.inbound=c(1.5,1));let $=M.tier==="edge"?2+Math.floor(a()*4):1+Math.floor(a()*3);for(let S=0;S<$;S++){let O=a(),D;O<.62?D=E.get(M.team):O<.82?D=b.get(M.domain):O<.94?D=w:D=h;let W=D.filter(j=>j.level>M.level&&(j.tier!=="data"||j.team===M.team||a()<.05));W.length&&T(M,d(W),M.inbound*l(.15,1.6))}if(M.tier!=="edge"){let S=E.get(M.team).filter(O=>O.tier==="data");S.length&&T(M,d(S),M.inbound*l(.8,2.5))}}for(let M=0;M<Math.round(h.length*.012);M++){let $=d(A),S=A.filter(O=>O.level<$.level&&O.domain===$.domain);S.length&&T($,d(S),$.inbound*l(.02,.1),!0)}let G=[],B=new Set,X=h.filter(M=>M.tier==="app"||M.tier==="core"),le=[],K=[],Ue=new Map,Ce=["created","updated","completed","failed","events","changes","snapshots","audit","metrics","commands"];for(let M=0;M<t;M++){let $=d(X),S=`${$.team}.${d(Ce)}`,O=2;for(;B.has(S);)S=`${$.team}.${d(Ce)}.v${O++}`;B.add(S);let D={name:S,domain:$.domain,team:$.team,partitions:d([3,6,12,24,48])};G.push(D);let W=new Set([$]);a()<.35&&W.add(d(E.get($.team).filter(ie=>ie.tier!=="data"))??$);let j=0;for(let ie of W){let _e=Math.max(.2,ie.inbound*l(.1,1.2));le.push({from:ie,topic:D,rps:_e}),j+=_e}let z=1+Math.floor(a()*4),de=a()<.15,Y=0,U=new Set;for(let ie=0;ie<z;ie++){let Kt=a()<.45?b.get($.domain):h,q=d(Kt.filter(Ut=>(Ut.tier==="app"||Ut.tier==="core")&&Ut.team!==$.team));if(!q||U.has(q.name))continue;U.add(q.name);let Xe=de&&ie===0,ln=j*(Xe?l(.45,.85):l(.97,1)),dn=Math.round(Xe?c(12,1):c(4,1.2));Y+=dn,K.push({topic:D,to:q,group:`${q.name}-cg`,rps:ln,lag:dn}),q.inbound+=ln}Ue.set(S,Y)}let _=[],Ee=[],$e=n.map((M,$)=>$===0?1:l(.4,.8)),Te=$e.reduce((M,$)=>M+$,0),Ne=M=>Math.round(M*1e4)/1e4;n.forEach((M,$)=>{let S=$e[$]/Te;for(let O of P)_.push({from:{service:O.from.name,api:O.from.tier==="edge"?"ingress":"call"},to:{service:O.to.name,api:O.api},dims:{env:"prod",region:M,transport:"sync",protocol:O.protocol,failure:O.failure},metrics:{rps:Math.round(O.rps*S*100)/100,latencyMs:Math.round(x(O.to.tier)*10)/10,errorRate:Ne(y(O.to.name,l(0,.003))),bytes:Math.round(l(200,6e3))}});for(let O of le)_.push({from:{service:O.from.name,api:"produce"},to:{service:O.topic.name,api:"produce"},dims:{env:"prod",region:M,transport:"kafka",protocol:"kafka",failure:"fail-open"},metrics:{rps:Math.round(O.rps*S*100)/100,latencyMs:Math.round(l(2,12)*10)/10,errorRate:Ne(l(0,.001)),bytes:Math.round(l(300,4e3))}});for(let O of K)_.push({from:{service:O.topic.name,api:O.group},to:{service:O.to.name,api:"consume"},dims:{env:"prod",region:M,transport:"kafka",protocol:"kafka",failure:"fail-open",group:O.group},metrics:{rps:Math.round(O.rps*S*100)/100,latencyMs:Math.round(l(5,80)*10)/10,errorRate:Ne(y(O.to.name,l(0,.004))),bytes:Math.round(l(300,4e3))}})});for(let M of h)Ee.push({key:{service:M.name},dims:{domain:M.domain,team:M.team,kind:M.kind,tier:M.tier,role:M.role,...M.engine?{engine:M.engine}:{}},attrs:{instances:M.tier==="data"?Math.round(l(3,9)):Math.round(c(2.2,.7)),cpu:l(.1,.9),mem:l(.2,.9)}});for(let M of G)Ee.push({key:{service:M.name},dims:{domain:M.domain,team:M.team,kind:"topic",tier:"core",role:"topic"},attrs:{partitions:M.partitions,lag:Ue.get(M.name)??0,retentionHours:d([24,72,168])}});return Ee.push({key:{service:"clients"},dims:{domain:"outside",team:"outside",kind:"client",tier:"edge",role:"client"},attrs:{instances:1}}),{version:1,records:_,nodes:Ee,meta:{title:o,window:"synthetic",hierarchy:["domain","team"],note:`${h.length} services, ${G.length} topics, ${P.length} sync edges, ${le.length+K.length} kafka edges, ${L.size} sick, ${N.size} burning`}}}var Os=["orders","pricing","catalog","search","identity","payments","shipping","inventory","reviews","notify","fraud","ledger","geo","recs","billing"],Ls=["orders-db","user-db","events","blob-store","ledger-db"];function Cn(r={}){let{seed:e=42,services:s=6,datastores:t=2,extraCalls:n=4,baseRps:i=4200,withErrors:o=!0,title:a="Randomly generated topology"}=r,l=ee(e),d=b=>b[Math.floor(l()*b.length)],c=(b,k)=>b+l()*(k-b),h=[{id:"client",label:"Clients",kind:"client",layer:0},{id:"edge-lb",label:"Edge LB",kind:"gateway",layer:1},{id:"gateway",label:"API gateway",kind:"gateway",layer:2}],p=[],u=[...Os];for(let b=0;b<s;b++){let k=Math.floor(l()*u.length),w=u.splice(k,1)[0]??`svc-${b}`;p.push(w),h.push({id:w,label:w,kind:"service"})}let g=[],m=[...Ls];for(let b=0;b<t;b++){let k=Math.floor(l()*m.length),w=m.splice(k,1)[0]??`store-${b}`;g.push(w),h.push({id:w,label:w,kind:"datastore"})}h.push({id:"cache",label:"redis",kind:"cache"});let f=[ct("client","edge-lb",i,c(1,3),"request"),ct("edge-lb","gateway",i*.99,c(1,4),"request")],v=p.map(()=>c(.4,3)),R=v.reduce((b,k)=>b+k,0);p.forEach((b,k)=>{let w=i*.99*v[k]/R;f.push(ct("gateway",b,w,c(4,60),"request",nn(o,l)))});for(let[b,k]of p.entries()){let w=l()<.4?"cache":g[b%g.length]??"cache",A=c(.8,2.6),L=f.find(N=>N.to===k)?.metrics.rps??100;f.push(ct(k,w,L*A,w==="cache"?c(.4,2):c(6,120),"request",nn(o,l)))}for(let b=0;b<n&&p.length>1;b++){let k=d(p),w=d(p);w===k&&(w=p[(p.indexOf(k)+1)%p.length]);let A=f.find(L=>L.to===k)?.metrics.rps??80;f.push(ct(k,w,A*c(.15,.9),c(5,90),"request",nn(o,l)))}let E=new Set;for(let b of f)E.add(b.from),E.add(b.to);return{version:1,nodes:h.filter(b=>E.has(b.id)),edges:f,meta:{title:a,window:"synthetic",generatedAt:new Date(0).toISOString(),seed:e}}}function nn(r,e){return r?e()<.25?e()*.06:e()*.004:0}function ct(r,e,s,t,n="request",i=0,o=1400){return{from:r,to:e,kind:n,metrics:{rps:Math.round(s*10)/10,latencyMs:Math.round(t*10)/10,errorRate:Math.round(i*1e4)/1e4,bytes:o}}}function $n(r=5e3){return{version:1,nodes:[{id:"client",label:"Clients",kind:"client",layer:0},{id:"lb",label:"Load balancer",kind:"gateway",layer:1},{id:"api-1",label:"api-1",kind:"service",layer:2},{id:"api-2",label:"api-2",kind:"service",layer:2},{id:"nonce",label:"nonce store",kind:"cache",layer:3},{id:"users",label:"user-db",kind:"datastore",layer:3},{id:"orders",label:"orders-db",kind:"datastore",layer:4}],edges:[{from:"client",to:"lb",label:"unauthenticated attempt",kind:"request",metrics:{rps:r,latencyMs:2,bytes:700}},{from:"lb",to:"api-1",kind:"request",metrics:{rps:r/2,latencyMs:3,bytes:700}},{from:"lb",to:"api-2",kind:"request",metrics:{rps:r/2,latencyMs:3,bytes:700}},{from:"api-1",to:"nonce",label:"issue nonce",kind:"request",metrics:{rps:r/2,latencyMs:1.4,bytes:120}},{from:"api-2",to:"nonce",label:"issue nonce",kind:"request",metrics:{rps:r/2,latencyMs:1.4,bytes:120}},{from:"lb",to:"client",label:"401 challenge",kind:"challenge",metrics:{rps:r,latencyMs:4,bytes:320}},{from:"client",to:"lb",id:"retry",label:"retry with credentials",kind:"retry",metrics:{rps:r,latencyMs:2,bytes:900}},{from:"api-1",to:"users",label:"verify password (KDF)",kind:"request",metrics:{rps:r/2,latencyMs:82,errorRate:.012,bytes:400}},{from:"api-2",to:"users",label:"verify password (KDF)",kind:"request",metrics:{rps:r/2,latencyMs:82,errorRate:.012,bytes:400}},{from:"api-1",to:"nonce",id:"nonce-check-1",label:"validate nonce (cross-node)",kind:"request",metrics:{rps:r/2,latencyMs:3.5,errorRate:.09,bytes:120}},{from:"api-2",to:"nonce",id:"nonce-check-2",label:"validate nonce (cross-node)",kind:"request",metrics:{rps:r/2,latencyMs:3.5,errorRate:.09,bytes:120}},{from:"api-1",to:"orders",label:"actual work",kind:"request",metrics:{rps:r/2,latencyMs:11,bytes:4200}},{from:"api-2",to:"orders",label:"actual work",kind:"request",metrics:{rps:r/2,latencyMs:11,bytes:4200}}],meta:{title:"Per-request 401 challenge",window:"modelled",note:"Every useful call costs two round trips and one password KDF."}}}function Nn(r=5e3){let e=r*.004;return{version:1,nodes:[{id:"client",label:"Clients",kind:"client",layer:0},{id:"lb",label:"Load balancer",kind:"gateway",layer:1},{id:"api-1",label:"api-1",kind:"service",layer:2},{id:"api-2",label:"api-2",kind:"service",layer:2},{id:"authz",label:"token issuer",kind:"service",layer:3},{id:"users",label:"user-db",kind:"datastore",layer:4},{id:"orders",label:"orders-db",kind:"datastore",layer:4}],edges:[{from:"client",to:"lb",label:"request with bearer token",kind:"request",metrics:{rps:r,latencyMs:2,bytes:900}},{from:"lb",to:"api-1",kind:"request",metrics:{rps:r/2,latencyMs:3,bytes:900}},{from:"lb",to:"api-2",kind:"request",metrics:{rps:r/2,latencyMs:3,bytes:900}},{from:"client",to:"authz",label:"token refresh (amortised)",kind:"request",metrics:{rps:e,latencyMs:95,bytes:700}},{from:"authz",to:"users",label:"verify password (KDF)",kind:"request",metrics:{rps:e,latencyMs:82,errorRate:.012,bytes:400}},{from:"api-1",to:"orders",label:"actual work",kind:"request",metrics:{rps:r/2,latencyMs:11,bytes:4200}},{from:"api-2",to:"orders",label:"actual work",kind:"request",metrics:{rps:r/2,latencyMs:11,bytes:4200}}],meta:{title:"Token auth, verified in-process",window:"modelled",note:"Signature check is microseconds and touches no shared state."}}}function Fn(r,e="inline"){return{name:e,load:async()=>r}}function Dn(r,{pollMs:e,fetchOptions:s}={}){let n={name:r,load:async()=>{let i=await fetch(r,s);if(!i.ok)throw new Error(`tracelight: ${r} responded ${i.status}`);return await i.json()}};return e&&e>0&&(n.subscribe=(i,o)=>sn(n,e,i,o)),n}function In(r){let e;return{name:r,load:()=>new Promise((s,t)=>{let n=new EventSource(r);n.onmessage=i=>{n.close();try{s(JSON.parse(i.data))}catch(o){t(o)}},n.onerror=()=>{n.close(),t(new Error(`tracelight: event source ${r} failed`))}}),subscribe(s,t){return e=new EventSource(r),e.onmessage=n=>{try{s(JSON.parse(n.data))}catch(i){t?.(i)}},e.onerror=()=>t?.(new Error(`tracelight: event source ${r} failed`)),()=>e?.close()},close:()=>e?.close()}}function sn(r,e,s,t){let n=!1,i,o=async()=>{try{let a=await r.load();n||s(a)}catch(a){n||t?.(a)}n||(i=setTimeout(o,e))};return o(),()=>{n=!0,clearTimeout(i)}}function It(r,e={}){let s=e.hierarchy??r.meta?.hierarchy??["domain","team"],t=e.leafKey??"service",n=e.where??{},i=new Map;for(let y of r.nodes??[]){let x=y.key[t];if(!x)continue;let P=i.get(x);i.set(x,{dims:{...P?.dims??{},...y.dims??{}},attrs:{...P?.attrs??{},...y.attrs??{}}})}let o=y=>{for(let[x,P]of Object.entries(n)){let C=y?.[x];if(C!==void 0&&(Array.isArray(P)?!P.includes(C):P!==C))return!1}return!0},a=new Map,l=new Set;for(let y of r.records){if(!o(y.dims))continue;let x=y.from[t],P=y.to[t];if(!x||!P||x===P)continue;l.add(x),l.add(P);let C=`${x}\0${P}`,T=a.get(C)??{rps:0,lat:0,err:0,bytes:0,async:!0,rigid:!0,apis:new Set,flows:0},F=y.metrics.rps;T.rps+=F,T.lat+=(y.metrics.latencyMs??0)*F,T.err+=(y.metrics.errorRate??0)*F,T.bytes+=(y.metrics.bytes??0)*F;let I=y.dims?.transport??y.dims?.protocol??"";/kafka|async|queue|sqs|pubsub|rabbit/i.test(I)||(T.async=!1),/closed|rigid|hard|required/i.test(y.dims?.failure??"")||(T.rigid=!1);let H=y.dims?.group??y.to.api??y.from.api;H&&T.apis.add(H),T.flows++,a.set(C,T)}for(let y of i.keys())l.add(y);let d=[],c=new Map,h=new Map;for(let y of l)c.set(y,[]),h.set(y,[]);for(let[y,x]of a){let[P,C]=y.split("\0"),T={id:`${P}>${C}`,from:P,to:C,rps:x.rps,latencyMs:x.rps>0?x.lat/x.rps:0,errorRate:x.rps>0?x.err/x.rps:0,bytes:x.rps>0?x.bytes/x.rps:0,async:x.async,rigid:x.rigid,back:!1,apis:[...x.apis].sort(),flows:x.flows};d.push(T),c.get(P).push(T.id),h.get(C).push(T.id)}let p=new Map(d.map(y=>[y.id,y])),u=new Map,g=new Map,m=y=>/topic|queue/i.test(i.get(y)?.dims.kind??""),f=y=>[...y].sort((x,P)=>p.get(P).rps-p.get(x).rps),v=y=>{if(g.get(y)===2)return u.get(y);if(g.get(y)===1)return-1;g.set(y,1);let x=0;for(let P of f(h.get(y)??[])){let C=p.get(P);if(C.async||m(C.from)||m(y))continue;let T=v(C.from);T<0?C.back=!0:x=Math.max(x,T+1)}return u.set(y,x),g.set(y,2),x},R=y=>(c.get(y)??[]).reduce((x,P)=>x+p.get(P).rps,0);for(let y of[...l].sort((x,P)=>R(P)-R(x)))v(y);let E=()=>{for(let y of l){if(!m(y))continue;let x=0;for(let P of h.get(y)??[])m(p.get(P).from)||(x=Math.max(x,(u.get(p.get(P).from)??0)+1));u.set(y,x)}};E();for(let y of l){if(m(y))continue;let x=(h.get(y)??[]).map(C=>p.get(C));if(x.some(C=>!C.async&&!C.back))continue;let P=u.get(y)??0;for(let C of x)C.async&&(P=Math.max(P,(u.get(C.from)??0)+1));u.set(y,P)}for(let y=0;y<32;y++){let x=!1;for(let P of d){if(P.async||P.back||m(P.from)||m(P.to))continue;let C=(u.get(P.from)??0)+1;(u.get(P.to)??0)<C&&(u.set(P.to,C),x=!0)}if(!x)break}E();for(let y of d)(u.get(y.to)??0)<=(u.get(y.from)??0)&&(y.back=!0);let b=Math.max(0,...u.values()),k=new Map;for(let y of l){let x=i.get(y),P=x?.dims??{},C=[],T="";for(let G of s){let B=P[G]??"\u2014";T=T?`${T}/${B}`:B,C.push(T)}let F=0,I=0;for(let G of h.get(y)??[]){let B=p.get(G);F+=B.rps,I+=B.errorRate*B.rps}let H=R(y);k.set(y,{id:y,label:y,kind:P.kind??"service",path:C,depth:u.get(y)??0,dims:P,attrs:x?.attrs??{},inRps:F,outRps:H,errorRate:F>0?I/F:0})}let w=new Map;for(let y of k.values())y.path.forEach((x,P)=>{let C=w.get(x);if(!C&&(C={id:x,label:x.slice(x.lastIndexOf("/")+1),level:P,parent:P>0?y.path[P-1]:null,groups:[],leaves:[],all:[],depthMin:1/0,depthMax:-1/0,rps:0,errorRate:0,lag:0},w.set(x,C),C.parent)){let T=w.get(C.parent);T.groups.includes(x)||T.groups.push(x)}C.all.push(y.id),P===y.path.length-1&&C.leaves.push(y.id),C.depthMin=Math.min(C.depthMin,y.depth),C.depthMax=Math.max(C.depthMax,y.depth),C.rps+=y.inRps,C.errorRate+=y.errorRate*y.inRps,C.lag=Math.max(C.lag,y.attrs.lag??0)});for(let y of w.values())y.errorRate=y.rps>0?y.errorRate/y.rps:0,Number.isFinite(y.depthMin)||(y.depthMin=y.depthMax=0);let A=y=>y.all.reduce((x,P)=>x+k.get(P).depth,0)/Math.max(1,y.all.length),L=y=>y.sort((x,P)=>A(w.get(x))-A(w.get(P))||x.localeCompare(P));for(let y of w.values())L(y.groups);let N=L([...w.values()].filter(y=>y.level===0).map(y=>y.id));return{leaves:k,groups:w,roots:N,edges:d,byId:p,out:c,inc:h,hierarchy:s,leafKey:t,maxDepth:b}}var ht=()=>({expanded:[],focus:null,hops:1,killed:[],pins:[]});function ut(r,e,s){let t=new Set([e]),n=[e];for(let i=0;i<s;i++){let o=[];for(let a of n){for(let l of r.out.get(a)??[]){let d=l.slice(l.indexOf(">")+1);t.has(d)||(t.add(d),o.push(d))}for(let l of r.inc.get(a)??[]){let d=l.slice(0,l.indexOf(">"));t.has(d)||(t.add(d),o.push(d))}}n=o}return t}function jn(r,e){let s=new Set(e.expanded);for(let t of[...s]){let n=r.groups.get(t);for(;n?.parent;)s.add(n.parent),n=r.groups.get(n.parent)}for(let t of Gn(r,e))for(let n of r.leaves.get(t).path)s.add(n);return s}function Gn(r,e){let s=new Set;if(e.focus&&r.leaves.has(e.focus))for(let t of ut(r,e.focus,e.hops))s.add(t);for(let t of e.pins??[])r.leaves.has(t)&&s.add(t);return s}var pt=r=>`${r}#rest`,Re=r=>r.endsWith("#rest")?r.slice(0,-5):r;function jt(r,e){let s=jn(r,e),t=jn(r,{...e,focus:null,pins:[]}),n=Gn(r,e),i=n.size?n:null,o=[],a=l=>{let d=r.groups.get(l);if(!s.has(l)){o.push({id:l,kind:"group",members:d.all,depthMin:d.depthMin,depthMax:d.depthMax});return}for(let p of d.groups)a(p);let c=i&&!t.has(l),h=[];for(let p of d.leaves){let u=r.leaves.get(p);if(c&&!i.has(p)){h.push(p);continue}o.push({id:p,kind:"leaf",members:[p],depthMin:u.depth,depthMax:u.depth})}if(h.length){let p=h.map(u=>r.leaves.get(u).depth);o.push({id:pt(l),kind:"group",members:h,depthMin:Math.min(...p),depthMax:Math.max(...p)})}};for(let l of r.roots)a(l);return{units:o,expanded:s}}function Gt(r,e,s=()=>!0){let t=new Map;for(let a of e)for(let l of a.members)t.set(l,a.id);let n=new Map,i=new Map;for(let a of r.edges){if(!s(a))continue;let l=t.get(a.from),d=t.get(a.to);if(!l||!d)continue;if(l===d){n.set(l,(n.get(l)??0)+a.rps);continue}let c=`${l}>${d}`,h=i.get(c);h||(h={id:c,from:l,to:d,rps:0,latencyMs:0,errorRate:0,bytes:0,async:!1,mixed:!1,rigid:!0,back:!0,leafEdges:[],fromLeaves:0,toLeaves:0,fromDepth:0,toDepth:0,lat:0,err:0,by:0,fd:0,td:0,syncSeen:!1,asyncSeen:!1,fl:new Set,tl:new Set},i.set(c,h)),h.rps+=a.rps,h.lat+=a.latencyMs*a.rps,h.err+=a.errorRate*a.rps,h.by+=a.bytes*a.rps,h.fd+=r.leaves.get(a.from).depth*a.rps,h.td+=r.leaves.get(a.to).depth*a.rps,a.async?h.asyncSeen=!0:h.syncSeen=!0,a.rigid||(h.rigid=!1),a.back||(h.back=!1),h.leafEdges.push(a),h.fl.add(a.from),h.tl.add(a.to)}let o=[];for(let a of i.values())o.push({id:a.id,from:a.from,to:a.to,rps:a.rps,latencyMs:a.rps>0?a.lat/a.rps:0,errorRate:a.rps>0?a.err/a.rps:0,bytes:a.rps>0?a.by/a.rps:0,async:a.asyncSeen&&!a.syncSeen,mixed:a.asyncSeen&&a.syncSeen,rigid:a.rigid,back:a.back,leafEdges:a.leafEdges,fromLeaves:a.fl.size,toLeaves:a.tl.size,fromDepth:a.rps>0?a.fd/a.rps:0,toDepth:a.rps>0?a.td/a.rps:0});return o.sort((a,l)=>l.rps-a.rps),{edges:o,unitOf:t,internal:n}}function Wt(r,e,s){if(!r.leaves.has(e)||!r.leaves.has(s))return null;let t=new Map([[e,0]]),n=new Map,i=new Set,o=l=>r.leaves.get(l).outRps||1;for(;;){let l=null,d=1/0;for(let[c,h]of t)!i.has(c)&&h<d&&(d=h,l=c);if(l===null)return null;if(l===s)break;i.add(l);for(let c of r.out.get(l)??[]){let h=r.byId.get(c),p=-Math.log(Math.max(1e-6,h.rps/o(l))),u=d+p;u<(t.get(h.to)??1/0)&&(t.set(h.to,u),n.set(h.to,l))}}let a=[s];for(;a[0]!==e;)a.unshift(n.get(a[0]));return a}function Bt(r,e){let s=new Set(e.filter(i=>r.leaves.has(i))),t=new Set,n=!0;for(;n;){n=!1;for(let i of r.edges)!s.has(i.to)||s.has(i.from)||(i.rigid?(s.add(i.from),t.delete(i.from),n=!0):t.has(i.from)||t.add(i.from))}return{dead:s,degraded:t}}function Ht(r,e,s=12){let t=e.trim().toLowerCase();if(!t)return[];let n=[];for(let i of r.leaves.values()){let o=i.id.toLowerCase().indexOf(t);o>=0&&n.push({id:i.id,kind:i.kind,label:i.id,path:i.path,score:o+(i.id.length-t.length)*.01})}for(let i of r.groups.values()){let o=i.label.toLowerCase().indexOf(t);o>=0&&n.push({id:i.id,kind:`group:${r.hierarchy[i.level]??"group"}`,label:i.label,path:i.parent?[i.parent]:[],score:o-.5})}return n.sort((i,o)=>i.score-o.score).slice(0,s)}var rn={colW:210,tileW:160,tileH:30,rowH:42,headerH:26,bandPad:12,gap:{0:44,1:12,other:6},x0:60,y0:40};function qt(r,e,s){let t=rn,n=u=>t.x0+u*t.colW,i=new Map,o=[],a=new Map(e.map(u=>[u.id,u])),l=new Map,d=u=>t.gap[u]??t.gap.other,c=(u,g)=>{let m=r.groups.get(u);if(!s.has(u)){let E=a.get(u),b=n(E.depthMin);return i.set(u,{x:b,y:g+(t.rowH-t.tileH)/2,w:n(E.depthMax)+t.tileW-b,h:t.tileH}),t.rowH}let f=g+t.headerH;for(let E of m.groups)f+=c(E,f)+d(m.level+1);if(m.leaves.length){let E=new Map;for(let A of m.leaves){if(!a.has(A))continue;let L=r.leaves.get(A).depth;E.set(L,[...E.get(L)??[],A])}let b=[...E.keys()].sort((A,L)=>A-L),k=0;for(let A of b){let L=E.get(A),N=y=>{let x=(r.inc.get(y)??[]).map(P=>r.byId.get(P).from).filter(P=>l.has(P));return x.length?x.reduce((P,C)=>P+l.get(C),0)/x.length:1e3-r.leaves.get(y).inRps/1e6};L.sort((y,x)=>N(y)-N(x)||r.leaves.get(x).inRps-r.leaves.get(y).inRps),L.forEach((y,x)=>{l.set(y,x),i.set(y,{x:n(A),y:f+x*t.rowH+(t.rowH-t.tileH)/2,w:t.tileW,h:t.tileH})}),k=Math.max(k,L.length)}f+=k*t.rowH;let w=a.get(pt(u));if(w){let A=n(w.depthMin);i.set(w.id,{x:A,y:f+(t.rowH-t.tileH)/2,w:n(w.depthMax)+t.tileW-A,h:t.tileH}),f+=t.rowH}}else f-=d(m.level+1);let v=n(m.depthMin)-t.bandPad,R=f+t.bandPad/2;return o.push({id:u,level:m.level,x:v,y:g,w:n(m.depthMax)+t.tileW+t.bandPad-v,h:R-g}),R-g},h=t.y0;for(let u of r.roots)h+=c(u,h)+d(0);o.sort((u,g)=>u.level-g.level);let p={x:0,y:0,w:n(r.maxDepth)+t.tileW+t.x0+t.bandPad*2,h:h+t.y0};return{rects:i,bands:o,world:p,colX:n,metrics:t}}var qn={traffic:{label:"Traffic",summary:"ribbon width and particles by rps; async flows as packets"},reliability:{label:"Reliability",summary:"red glow by error rate, topic lag as pipe fill, dead fraction on tiles"},latency:{label:"Latency",summary:"ribbons heat from cool to warm as latency grows"},ownership:{label:"Ownership",summary:"everything coloured by its top-level group"},kafka:{label:"Kafka",summary:"only async flows: producers, topics, consumer groups, lag"},blast:{label:"Blast radius",summary:"rigid (fail-closed) vs breakaway (fail-open); kill a service to propagate"}},Wn=520,Cs=460,$s=90,Ns=360,Vt=class{constructor(e,s={}){this.model=null;this.state=ht();this.units=[];this.unitById=new Map;this.expanded=new Set;this.layoutResult=null;this.edges=[];this.unitOf=new Map;this.internal=new Map;this.domainRps={min:1,max:1e3};this.health={dead:new Set,degraded:new Set};this.trailIds=null;this.trailEdges=new Set;this.ego=null;this.shapes=new Map;this.shapesFrom=new Map;this.shapesTo=new Map;this.morphStart=0;this.morphing=!1;this.appearing=new Set;this.camera={x:0,y:0,scale:1};this.cameraFrom=null;this.cameraTo=null;this.cameraStart=0;this.fitScale=1;this.views=[];this.particles=new Map;this.dash=0;this.width=0;this.height=0;this.dpr=1;this.raf=0;this.last=0;this.hovered=null;this.pointer=null;this.drag=null;this.lastSemantic=0;this.groupColour=new Map;this.onDown=e=>{let s=this.canvas.getBoundingClientRect();this.drag={x:e.clientX-s.left,y:e.clientY-s.top,cx:this.camera.x,cy:this.camera.y,moved:!1},this.cameraTo=null,this.canvas.setPointerCapture(e.pointerId),this.canvas.style.cursor="grabbing"};this.onMove=e=>{let s=this.canvas.getBoundingClientRect(),t=e.clientX-s.left,n=e.clientY-s.top;if(this.pointer={x:t,y:n},this.drag){let a=t-this.drag.x,l=n-this.drag.y;Math.hypot(a,l)>3&&(this.drag.moved=!0),this.drag.moved&&(this.camera.x=this.drag.cx-a/this.camera.scale,this.camera.y=this.drag.cy-l/this.camera.scale,this.opts.onCamera?.());return}let i=this.hit(t,n),o=i?.id!==this.hovered?.id||i?.kind!==this.hovered?.kind;this.hovered=i,this.canvas.style.cursor=i?"pointer":"grab",o&&this.opts.onHover?.(i)};this.onUp=e=>{let s=this.drag;if(this.drag=null,this.canvas.style.cursor="grab",!s||s.moved)return;let t=this.canvas.getBoundingClientRect(),n=this.hit(e.clientX-t.left,e.clientY-t.top);if(!n){this.state.focus&&this.focus(null);return}n.kind==="unit"?this.unitById.get(n.id)?.kind==="group"?this.expand(n.id):this.focus(this.state.focus===n.id?null:n.id):n.kind==="band"?this.collapse(n.id):n.kind==="edge"&&this.opts.onSelect?.(n.id)};this.onLeave=()=>{this.pointer=null,this.hovered&&this.opts.onHover?.(null),this.hovered=null,this.tooltip.style.opacity="0"};this.onDbl=e=>{let s=this.canvas.getBoundingClientRect(),t=this.hit(e.clientX-s.left,e.clientY-s.top);t?.kind==="unit"?this.unitById.get(t.id)?.kind==="group"?this.expand(t.id):this.fit(t.id):t?.kind==="band"&&this.collapse(t.id)};this.onWheel=e=>{e.preventDefault();let s=this.canvas.getBoundingClientRect(),t=e.clientX-s.left,n=e.clientY-s.top;if(e.ctrlKey||e.metaKey||Math.abs(e.deltaY)>=Math.abs(e.deltaX)){let i=Math.exp(-e.deltaY*(e.ctrlKey?.01:.0022));this.zoomAt(t,n,i)}else this.panBy(e.deltaX,0)};this.container=e,this.opts=s,this.theme=me(s.theme??"auto"),this.lens=s.lens??"traffic",this.rand=ee(s.seed??3),getComputedStyle(e).position==="static"&&(e.style.position="relative"),this.canvas=document.createElement("canvas"),Object.assign(this.canvas.style,{display:"block",width:"100%",height:"100%",cursor:"grab",touchAction:"none"}),e.appendChild(this.canvas);let t=this.canvas.getContext("2d");if(!t)throw new Error("atlas: 2d canvas unavailable");this.ctx=t,this.tooltip=document.createElement("div"),Object.assign(this.tooltip.style,{position:"absolute",pointerEvents:"none",opacity:"0",transition:"opacity 90ms ease",font:"12px/1.45 ui-monospace, SFMono-Regular, Menlo, monospace",padding:"8px 10px",borderRadius:"7px",whiteSpace:"pre",zIndex:"2",maxWidth:"360px",boxShadow:"0 6px 20px rgba(0,0,0,.28)",background:this.theme.tooltipBg,color:this.theme.tooltipText}),e.appendChild(this.tooltip),this.canvas.addEventListener("pointerdown",this.onDown),this.canvas.addEventListener("pointermove",this.onMove),this.canvas.addEventListener("pointerup",this.onUp),this.canvas.addEventListener("pointerleave",this.onLeave),this.canvas.addEventListener("wheel",this.onWheel,{passive:!1}),this.canvas.addEventListener("dblclick",this.onDbl),typeof ResizeObserver<"u"&&(this.resizeObserver=new ResizeObserver(()=>this.resize()),this.resizeObserver.observe(e)),this.resize(),this.start()}setTable(e,s={}){this.model=It(e,{...this.opts,...s}),this.groupColour.clear(),this.model.roots.forEach((t,n)=>this.groupColour.set(t,Ge[n%Ge.length])),this.state=ht(),this.shapes.clear(),this.rebuild(!1),this.fit(void 0,!1)}getModel(){return this.model}getState(){return{...this.state,expanded:[...this.state.expanded],killed:[...this.state.killed],pins:[...this.state.pins??[]]}}setState(e,s){this.state={...this.state,...e,expanded:[...e.expanded??this.state.expanded],killed:[...e.killed??this.state.killed],pins:[...e.pins??this.state.pins??[]]},this.rebuild(!0),s!==void 0&&this.fit(s??void 0)}getLens(){return this.lens}setLens(e){let s=this.lens;this.lens=e,this.particles.clear(),s==="kafka"!=(e==="kafka")&&this.rebuild(!1)}setTheme(e){this.theme=me(e),this.tooltip.style.background=this.theme.tooltipBg,this.tooltip.style.color=this.theme.tooltipText}expand(e,s=!0){e=Re(e),this.model?.groups.has(e)&&(this.expanded.has(e)&&this.state.expanded.includes(e)||this.setState({expanded:[...this.state.expanded,e]},s?e:void 0))}collapse(e,s=!0){if(!this.model?.groups.has(e))return;let t=new Set([e]);for(let o of this.state.expanded)o.startsWith(e+"/")&&t.add(o);let n=this.state.expanded.filter(o=>!t.has(o)),i=this.state.focus&&this.model.leaves.get(this.state.focus)?.path.includes(e)?null:this.state.focus;this.setState({expanded:n,focus:i},s?e:void 0)}toggle(e){this.expanded.has(e)?this.collapse(e):this.expand(e)}up(){if(!this.model)return;let e=this.state.focus?this.model.leaves.get(this.state.focus)?.path??[]:[];if(this.state.focus){this.focus(null);let t=[...e].reverse().find(n=>this.expanded.has(n));t&&this.fit(t);return}let s=this.state.expanded[this.state.expanded.length-1];s?this.collapse(s):this.fit()}collapseAll(){this.setState({expanded:[],focus:null},null)}focus(e,s){if(e&&!this.model?.leaves.has(e)){this.model?.groups.has(e)&&this.expand(e);return}this.setState({focus:e,hops:s??this.state.hops},e?`ego:${e}`:void 0),this.opts.onSelect?.(e)}setHops(e){this.setState({hops:Math.max(0,Math.min(4,e))},this.state.focus?`ego:${this.state.focus}`:void 0)}kill(e){let s=[...new Set([...this.state.killed,...e])];this.setState({killed:s})}revive(){this.setState({killed:[]})}setTrail(e,s){if(!this.model)return null;let t=Wt(this.model,e,s);if(this.trailIds=t?new Set(t):null,this.trailEdges=new Set,t){for(let n=0;n<t.length-1;n++)this.trailEdges.add(`${t[n]}>${t[n+1]}`);this.setState({pins:t,focus:null},"trail")}return t}clearTrail(){let e=this.trailIds;this.trailIds=null,this.trailEdges=new Set,e&&this.setState({pins:[]})}search(e,s=12){return this.model?Ht(this.model,e,s):[]}goTo(e){if(this.model)if(this.model.leaves.has(e)){let s=new Set(this.state.expanded);for(let t of this.model.leaves.get(e).path)s.add(t);this.setState({expanded:[...s],focus:e},`ego:${e}`),this.opts.onSelect?.(e)}else this.model.groups.has(e)&&this.expand(e)}breadcrumb(){if(!this.model)return[];let e=[];if(this.state.focus&&this.model.leaves.has(this.state.focus))e.push(...this.model.leaves.get(this.state.focus).path,this.state.focus);else{let s=this.state.expanded[this.state.expanded.length-1];if(s){let t=this.model.groups.get(s),n=[];for(;t;)n.unshift(t.id),t=t.parent?this.model.groups.get(t.parent):void 0;e.push(...n)}}return e.map(s=>{let t=this.model.groups.get(s);return t?{id:s,label:t.label,kind:this.model.hierarchy[t.level]??"group"}:{id:s,label:s,kind:this.model.leaves.get(s)?.kind??"leaf"}})}inspect(e){let s=this.model;if(!s)return null;e=Re(e);let t=s.leaves.get(e),n=s.groups.get(e);if(!t&&!n)return null;let i=t?[e]:n.all,o=new Set(i),a=new Map,l=new Map,d=0,c=0,h=0,p=(m,f,v)=>{let R=m.get(f)??{id:f,label:f,kind:s.leaves.get(f)?.kind??"service",rps:0,latencyMs:0,errorRate:0,async:v.async,rigid:v.rigid,apis:[],lat:0,err:0};R.rps+=v.rps,R.lat+=v.latencyMs*v.rps,R.err+=v.errorRate*v.rps,R.async=R.async&&v.async,R.rigid=R.rigid||v.rigid,R.apis=[...new Set([...R.apis,...v.apis])],m.set(f,R)};for(let m of i){for(let f of s.inc.get(m)??[]){let v=s.byId.get(f);o.has(v.from)||(d+=v.rps,h+=v.errorRate*v.rps,p(a,v.from,v))}for(let f of s.out.get(m)??[]){let v=s.byId.get(f);o.has(v.to)||(c+=v.rps,p(l,v.to,v))}}let u=m=>[...m.values()].map(f=>({id:f.id,label:f.label,kind:f.kind,rps:f.rps,latencyMs:f.rps?f.lat/f.rps:0,errorRate:f.rps?f.err/f.rps:0,async:f.async,rigid:f.rigid,apis:f.apis})).sort((f,v)=>v.rps-f.rps),g=this.health.dead.has(e)?"dead":this.health.degraded.has(e)?"degraded":"ok";return t?{id:e,kind:"leaf",label:t.label,path:t.path,dims:t.dims,attrs:t.attrs,inRps:t.inRps,outRps:t.outRps,errorRate:t.errorRate,members:1,upstream:u(a),downstream:u(l),health:g}:{id:e,kind:"group",label:n.label,path:n.parent?[n.parent]:[],dims:{},attrs:{lag:n.lag},inRps:d,outRps:c,errorRate:d?h/d:0,members:i.length,upstream:u(a),downstream:u(l),health:g}}fit(e,s=!0){if(!this.layoutResult)return;let t=this.layoutResult,n=null;e?e.startsWith("ego:")?n=this.egoRect(e.slice(4)):e==="trail"?n=this.trailIds?this.union([...this.trailIds].map(i=>this.shapesTo.get(i)??t.rects.get(i))):t.world:n=this.shapesTo.get(e)??t.rects.get(e)??t.bands.find(i=>i.id===e)??null:n=t.world,n||(n=t.world),this.zoomTo(n,s)}zoomTo(e,s=!0){let t={top:24,right:24,bottom:24,left:24,...this.opts.insets},n=Math.max(100,this.width-t.left-t.right),i=Math.max(100,this.height-t.top-t.bottom),o=V(Math.min(n/Math.max(1,e.w),i/Math.max(1,e.h)),this.fitScale*.5,1.35),a=t.left+n/2,l=t.top+i/2,d={x:e.x+e.w/2-a/o,y:e.y+e.h/2-l/o,scale:o};if(!s||!(this.opts.animate??!0)){this.camera=d,this.cameraTo=null;return}this.cameraFrom={...this.camera},this.cameraTo=d,this.cameraStart=ge()}getCamera(){return{...this.camera}}panBy(e,s){this.cameraTo=null,this.camera.x+=e/this.camera.scale,this.camera.y+=s/this.camera.scale}drawMinimap(e){let s=this.layoutResult,t=this.model,n=e.getContext("2d");if(!s||!t||!n)return;let i=window.devicePixelRatio||1,o=e.clientWidth,a=e.clientHeight;e.width!==Math.round(o*i)&&(e.width=Math.round(o*i),e.height=Math.round(a*i)),n.setTransform(i,0,0,i,0,0),n.clearRect(0,0,o,a);let l=Math.min(o/s.world.w,a/s.world.h),d=(o-s.world.w*l)/2,c=(a-s.world.h*l)/2,h=this.theme;for(let g of s.bands)n.fillStyle=g.level===0?h.surface:h.grid,n.globalAlpha=.9,n.fillRect(d+g.x*l,c+g.y*l,g.w*l,g.h*l);for(let g of this.units){let m=this.shapes.get(g.id)??s.rects.get(g.id);if(!m)continue;let f=this.errorOf(g),v=f>0?J(f,he,Oe):0;n.globalAlpha=1,n.fillStyle=v>0?h.error:this.lens==="ownership"?this.colourOf(g.id):h.edge,n.globalAlpha=v>0?.35+v*.65:.5,n.fillRect(d+m.x*l,c+m.y*l,Math.max(2,m.w*l),Math.max(2,m.h*l))}n.globalAlpha=1,n.strokeStyle=h.highlight,n.lineWidth=1;let p=d+this.camera.x*l,u=c+this.camera.y*l;n.strokeRect(p+.5,u+.5,this.width/this.camera.scale*l,this.height/this.camera.scale*l)}minimapClick(e,s,t){let n=this.layoutResult;if(!n)return;let i=e.clientWidth,o=e.clientHeight,a=Math.min(i/n.world.w,o/n.world.h),l=(i-n.world.w*a)/2,d=(o-n.world.h*a)/2,c=(s-l)/a,h=(t-d)/a;this.cameraFrom={...this.camera},this.cameraTo={x:c-this.width/2/this.camera.scale,y:h-this.height/2/this.camera.scale,scale:this.camera.scale},this.cameraStart=ge()}toDataURL(e="image/png"){return this.canvas.toDataURL(e)}stats(){let e=0;for(let s of this.particles.values())e+=s.list.length;return{units:this.units.length,edges:this.edges.length,leaves:this.model?.leaves.size??0,particles:e}}destroy(){cancelAnimationFrame(this.raf),this.resizeObserver?.disconnect(),this.canvas.remove(),this.tooltip.remove()}rebuild(e){let s=this.model;if(!s)return;let{units:t,expanded:n}=jt(s,this.state);this.units=t,this.unitById=new Map(t.map(d=>[d.id,d])),this.expanded=n;let i=qt(s,t,n);this.layoutResult=i;let o=Gt(s,t,this.lens==="kafka"?d=>d.async:void 0);this.edges=o.edges,this.unitOf=o.unitOf,this.internal=o.internal,this.domainRps=ve(this.edges.map(d=>d.rps)),this.health=Bt(s,this.state.killed),this.ego=this.state.focus?ut(s,this.state.focus,this.state.hops):null;let a=new Map;for(let[d,c]of i.rects)a.set(d,c);for(let d of i.bands)a.set(d.id,{x:d.x,y:d.y,w:d.w,h:d.h});let l=new Map;this.appearing.clear();for(let d of a.keys()){let c=this.shapes.get(d);if(c){l.set(d,c);continue}l.set(d,this.ancestorShape(d)??a.get(d)),this.appearing.add(d)}this.shapesFrom=l,this.shapesTo=a,e&&(this.opts.animate??!0)?(this.morphStart=ge(),this.morphing=!0):(this.shapes=new Map(a),this.morphing=!1),this.fitScale=this.computeFitScale();for(let d of[...this.particles.keys()])this.edges.some(c=>c.id===d)||this.particles.delete(d);this.opts.onState?.(this.getState())}ancestorShape(e){let s=this.model;e=Re(e);let t=s.leaves.get(e)?.path??(()=>{let n=[],i=s.groups.get(e);for(;i?.parent;)n.unshift(i.parent),i=s.groups.get(i.parent);return n})();for(let n=t.length-1;n>=0;n--){let i=this.shapes.get(t[n]);if(i)return i}return null}egoRect(e){if(!this.model||!this.ego)return null;let s=new Set;for(let t of this.ego)s.add(this.unitOf.get(t)??t);return this.union([...s].map(t=>this.shapesTo.get(t)))}union(e){let s=1/0,t=1/0,n=-1/0,i=-1/0;for(let o of e)o&&(s=Math.min(s,o.x),t=Math.min(t,o.y),n=Math.max(n,o.x+o.w),i=Math.max(i,o.y+o.h));return Number.isFinite(s)?{x:s-20,y:t-20,w:n-s+40,h:i-t+40}:null}start(){let e=()=>{this.raf=requestAnimationFrame(e);let s=ge(),t=Math.min(.05,(s-(this.last||s))/1e3);this.last=s,this.step(t),this.draw()};this.raf=requestAnimationFrame(e)}step(e){if(this.morphing){let s=V((ge()-this.morphStart)/Wn,0,1),t=on(s);for(let[n,i]of this.shapesTo){let o=this.shapesFrom.get(n)??i;this.shapes.set(n,{x:o.x+(i.x-o.x)*t,y:o.y+(i.y-o.y)*t,w:o.w+(i.w-o.w)*t,h:o.h+(i.h-o.h)*t})}for(let n of[...this.shapes.keys()])this.shapesTo.has(n)||this.shapes.delete(n);s>=1&&(this.morphing=!1)}if(this.cameraTo&&this.cameraFrom){let s=V((ge()-this.cameraStart)/Cs,0,1),t=on(s),n=this.cameraFrom,i=this.cameraTo,o=n.scale*Math.pow(i.scale/n.scale,t);this.camera={x:n.x+(i.x-n.x)*t,y:n.y+(i.y-n.y)*t,scale:o},s>=1&&(this.camera={...i},this.cameraTo=null),this.opts.onCamera?.()}this.dash+=e*28,this.buildViews(),this.stepParticles(e)}buildViews(){let e=this.edges.filter(d=>this.showEdge(d)),s=this.layoutResult,t=s.metrics.tileW/2,n=d=>{let c=this.shapes.get(d.from),h=this.shapes.get(d.to);return h.x>=c.x+c.w-2?"h":Math.abs(c.y-h.y)<1?"arc":"v"},i=new Map,o=(d,c,h)=>{i.set(d,[...i.get(d)??[],{e:c,key:h}])},a=new Map;for(let d of e){let c=this.shapes.get(d.from),h=this.shapes.get(d.to);if(!c||!h)continue;let p=n(d);a.set(d.id,p),p==="h"&&(o(`${d.from}:right`,d,h.y+h.h/2),o(`${d.to}:left`,d,c.y+c.h/2))}let l=new Map;for(let[d,c]of i)c.sort((h,p)=>h.key-p.key),c.forEach((h,p)=>l.set(`${d}:${h.e.id}`,{i:p,n:c.length}));this.views=[];for(let d of e){let c=this.shapes.get(d.from),h=this.shapes.get(d.to);if(!c||!h)continue;let p=Ie(d.rps,this.domainRps,{min:.7,max:8}),u=a.get(d.id),g,m,f,v;if(u==="h"){let E=l.get(`${d.from}:right:${d.id}`),b=l.get(`${d.to}:left:${d.id}`);g={x:c.x+c.w,y:c.y+c.h*(E.i+1)/(E.n+1)},m={x:h.x,y:h.y+h.h*(b.i+1)/(b.n+1)};let k=Math.max(40,(m.x-g.x)*.5);f={x:g.x+k,y:g.y},v={x:m.x-k,y:m.y}}else if(u==="v"){let E=h.y>c.y,b=V(s.colX(d.fromDepth)+t,c.x+10,c.x+c.w-10),k=V(s.colX(d.toDepth)+t,h.x+10,h.x+h.w-10);g={x:b,y:E?c.y+c.h:c.y},m={x:k,y:E?h.y:h.y+h.h};let w=(m.y-g.y)*.5;f={x:b,y:g.y+w},v={x:k,y:m.y-w}}else{g={x:c.x+c.w*.75,y:c.y},m={x:h.x+h.w*.25,y:h.y};let E=60+Math.abs(g.x-m.x)*.12;f={x:g.x+30,y:g.y-E},v={x:m.x-30,y:m.y-E}}let R=Math.hypot(m.x-g.x,m.y-g.y)*1.1;this.views.push({edge:d,a:g,b:m,c1:f,c2:v,width:p,length:R})}}showEdge(e){return this.lens!=="kafka"||e.async}stepParticles(e){if(!(this.opts.animate??!0)||this.lens==="ownership"){this.particles.clear();return}let s=this.views.filter(n=>this.isLeafEdge(n.edge)||this.ego&&this.touchesEgo(n.edge)).sort((n,i)=>(this.touchesEgo(n.edge)?1:0)-(this.touchesEgo(i.edge)?1:0)||i.edge.rps-n.edge.rps).slice(0,$s),t=new Set;for(let n of s){t.add(n.edge.id);let i=this.particles.get(n.edge.id);i||(i={list:[],acc:0},this.particles.set(n.edge.id,i));let o=be(n.edge.rps,this.domainRps)*(n.edge.async?.35:.55);i.acc+=o*e;let a=we(n.edge.latencyMs)/Math.max(60,n.length);for(;i.acc>=1&&i.list.length<60;)i.acc-=1,i.list.push({t:0,speed:a*(.9+this.rand()*.2),err:this.rand()<n.edge.errorRate});i.acc>4&&(i.acc=4);for(let l of i.list)l.t+=l.speed*e;i.list=i.list.filter(l=>l.t<1)}for(let n of[...this.particles.keys()])t.has(n)||this.particles.delete(n)}isLeafEdge(e){return this.unitById.get(e.from)?.kind==="leaf"||this.unitById.get(e.to)?.kind==="leaf"}touchesEgo(e){return this.ego?e.leafEdges.some(s=>this.ego.has(s.from)&&this.ego.has(s.to)):!1}resize(){let e=this.container.getBoundingClientRect();this.width=Math.max(1,Math.round(e.width)),this.height=Math.max(1,Math.round(e.height)),this.dpr=window.devicePixelRatio||1,this.canvas.width=Math.round(this.width*this.dpr),this.canvas.height=Math.round(this.height*this.dpr),this.layoutResult&&(this.fitScale=this.computeFitScale())}computeFitScale(){let e=this.layoutResult,s={top:24,right:24,bottom:24,left:24,...this.opts.insets};return Math.min((this.width-s.left-s.right)/Math.max(1,e.world.w),(this.height-s.top-s.bottom)/Math.max(1,e.world.h))}toScreen(e,s){return{x:(e-this.camera.x)*this.camera.scale,y:(s-this.camera.y)*this.camera.scale}}toWorld(e,s){return{x:e/this.camera.scale+this.camera.x,y:s/this.camera.scale+this.camera.y}}draw(){let{ctx:e,theme:s}=this;if(e.setTransform(this.dpr,0,0,this.dpr,0,0),e.fillStyle=s.background,e.fillRect(0,0,this.width,this.height),!this.model||!this.layoutResult)return;let t=this.camera.scale;e.save(),e.translate(-this.camera.x*t,-this.camera.y*t),e.scale(t,t),this.drawColumns(),this.drawBands();let n=this.state.focus?this.unitOf.get(this.state.focus)??this.state.focus:null,i=this.hovered?.kind==="unit"?this.hovered.id:null,o=this.hovered?.kind==="edge"?this.hovered.id:null,a=h=>{let p=this.unitById.get(h);return p?p.kind==="leaf"?10:h.endsWith("#rest")?5:this.model.groups.get(h)?.level??0:0},l=[...this.views].map(h=>({v:h,p:Math.max(a(h.edge.from),a(h.edge.to))})).sort((h,p)=>p.p-h.p||p.v.edge.rps-h.v.edge.rps).map(h=>h.v),d=V(Math.round(this.units.length*1.6),14,Ns),c=new Set(l.slice(0,d).map(h=>h.edge.id));for(let h of l.reverse()){let p=h.edge,u=p.from===n||p.to===n||p.from===i||p.to===i||p.id===o,g=this.ego?this.touchesEgo(p):!0,m=this.trailIds?p.leafEdges.some(R=>this.trailEdges.has(R.id)):!1,f=1;this.trailIds?f=m?1:.08:this.ego?f=g?1:.1:(i||o)&&(f=u?1:.35);let v=!c.has(p.id)&&!u&&!m;this.drawEdge(h,f,v,u||m)}for(let h of this.views)this.particles.has(h.edge.id)&&this.drawParticles(h);for(let h of this.units)this.drawUnit(h,n,i);e.restore(),this.updateTooltip()}drawColumns(){let{ctx:e,theme:s}=this,t=this.layoutResult,n=this.camera.scale;if(t.metrics.colW*n<30)return;e.save(),e.strokeStyle=s.grid,e.lineWidth=1/n,e.fillStyle=s.textMuted,e.font=`${11/n}px ui-monospace, Menlo, monospace`,e.globalAlpha=.9;let i=this.camera.y;for(let o=0;o<=this.model.maxDepth;o++){let a=t.colX(o)-t.metrics.bandPad-4;e.beginPath(),e.moveTo(a,t.world.y),e.lineTo(a,t.world.h),e.stroke(),n>.35&&e.fillText(o===0?"ingress":`depth ${o}`,t.colX(o),i+14/n)}e.restore()}drawBands(){let{ctx:e,theme:s}=this,t=this.camera.scale;for(let n of this.layoutResult.bands){let i=this.shapes.get(n.id)??n,o=this.model.groups.get(n.id),a=this.colourOf(n.id);if(e.save(),e.globalAlpha=n.level===0?.55:.5,e.fillStyle=n.level===0?s.surface:s.grid,pe(e,i.x,i.y,i.w,i.h,10),e.fill(),e.globalAlpha=1,e.strokeStyle=s.border,e.lineWidth=1/t,e.stroke(),e.fillStyle=a,e.globalAlpha=this.lens==="ownership"?.9:.55,pe(e,i.x,i.y,4,i.h,2),e.fill(),i.h*t>14){e.globalAlpha=1;let l=V(10.5/t,12,16);e.font=`600 ${l}px ui-sans-serif, system-ui, sans-serif`,e.fillStyle=s.text;let d=`${o.label}`;e.fillText(d,i.x+12,i.y+l+4);let c=e.measureText(d).width;e.font=`${l*.85}px ui-monospace, Menlo, monospace`,e.fillStyle=s.textMuted;let h=`${this.model.hierarchy[o.level]??"group"} \xB7 ${o.all.length} \xB7 ${Z(o.rps)}`;e.fillText(h,i.x+12+c+10,i.y+l+4),e.fillStyle=s.textMuted,e.fillText("\u2212",i.x+i.w-16,i.y+l+4)}e.restore()}}drawEdge(e,s,t,n){let{ctx:i,theme:o}=this,a=e.edge,l=this.camera.scale,d=this.edgeColour(a),c=this.health.dead.has(a.to)||this.health.dead.has(a.from)||a.leafEdges.some(p=>this.health.dead.has(p.to));i.save(),i.globalAlpha=s*(t?.2:.85),i.lineCap="round",i.beginPath(),i.moveTo(e.a.x,e.a.y),i.bezierCurveTo(e.c1.x,e.c1.y,e.c2.x,e.c2.y,e.b.x,e.b.y);let h=t?Math.max(.5/l,.5):Math.max(e.width,.7/l);if(this.lens==="blast"&&!c?a.rigid?(i.strokeStyle=d,i.lineWidth=h+2,i.globalAlpha*=.35,i.stroke(),i.globalAlpha=s):i.setLineDash([9,7]):a.async?(i.setLineDash([Math.max(4,h*1.6),Math.max(6,h*2.2)]),i.lineDashOffset=-this.dash):a.mixed&&(i.setLineDash([14,4]),i.lineDashOffset=-this.dash),c?(i.strokeStyle=o.error,i.setLineDash([3,5])):i.strokeStyle=n?this.brighten(d):d,i.lineWidth=h,i.stroke(),!t&&h>3&&!c&&(i.setLineDash([]),i.globalAlpha=s*.12,i.lineWidth=h+6,i.stroke()),!t&&a.async&&l>.8&&(this.lens==="kafka"||n)&&a.leafEdges.length===1){let p=a.leafEdges[0].apis[0];if(p&&/-cg$|group/i.test(p)){let u=an(e,.5);i.setLineDash([]),i.globalAlpha=s,i.font=`${10/l}px ui-monospace, Menlo, monospace`,i.fillStyle=o.textMuted,i.fillText(p,u.x+4/l,u.y-4/l)}}i.restore()}drawParticles(e){let{ctx:s,theme:t}=this,n=this.particles.get(e.edge.id);if(!n)return;let i=this.camera.scale,o=V(2.1/Math.sqrt(i),1.2,3.4),a=this.edgeColour(e.edge);s.save();for(let l of n.list){let d=an(e,l.t);s.fillStyle=l.err?t.error:a,s.globalAlpha=.95,s.beginPath(),s.arc(d.x,d.y,o,0,Math.PI*2),s.fill(),l.err&&(s.strokeStyle=t.error,s.lineWidth=1/i,s.globalAlpha=.6,s.beginPath(),s.arc(d.x,d.y,o*2,0,Math.PI*2),s.stroke())}s.restore()}drawUnit(e,s,t){let{ctx:n,theme:i}=this,o=this.shapes.get(e.id);if(!o)return;let a=this.camera.scale,l=this.model,d=e.kind==="leaf"?l.leaves.get(e.id):void 0,c=e.kind==="group"?l.groups.get(e.id):void 0,h=d?.kind==="topic",p=e.members.every(w=>this.health.dead.has(w)),u=e.members.filter(w=>this.health.dead.has(w)).length/e.members.length,g=!p&&e.members.some(w=>this.health.degraded.has(w)),m=!this.ego||e.members.some(w=>this.ego.has(w)),f=this.trailIds?e.members.some(w=>this.trailIds.has(w)):!0,v=1;this.trailIds?v=f?1:.25:this.ego&&(v=m?1:.35);let R=this.appearing.has(e.id)&&this.morphing?on(V((ge()-this.morphStart)/Wn,0,1)):1;v*=.15+R*.85,n.save(),n.globalAlpha=v;let E=this.errorOf(e),b=E>0?J(E,he,Oe):0;b>0&&this.lens!=="ownership"&&this.drawGlow(o,b*(this.lens==="reliability"?1:.8),v);let k=this.fillOf(e,d?.kind??"group");if(h?this.drawRail(o,d,k,v):(n.fillStyle=k,n.strokeStyle=e.id===s?i.highlight:e.id===t?i.text:i.border,n.lineWidth=(e.id===s?2:1)/Math.max(.5,Math.min(1,a)),pe(n,o.x,o.y,o.w,o.h,e.kind==="group"?8:d?.kind==="datastore"||d?.kind==="cache"?14:6),n.fill(),n.stroke(),e.kind==="group"&&(n.fillStyle=this.colourOf(e.id),n.globalAlpha=v*.9,pe(n,o.x,o.y,5,o.h,2),n.fill(),n.globalAlpha=v)),p?this.hatch(o,v):u>0&&(n.fillStyle=i.error,n.globalAlpha=v*.8,n.fillRect(o.x+6,o.y+o.h-4,(o.w-12)*u,3),n.globalAlpha=v),g&&(n.strokeStyle="#f59e0b",n.setLineDash([4,3]),n.lineWidth=1.5/Math.max(.5,a),pe(n,o.x-3,o.y-3,o.w+6,o.h+6,9),n.stroke(),n.setLineDash([])),o.h*a>=7){let w=V(10.5/a,12,o.h*.62);n.font=`${e.kind==="group"?600:500} ${w}px ui-sans-serif, system-ui, sans-serif`,n.fillStyle=i.nodeText,n.textBaseline="middle";let A=e.kind==="group"?c?c.label:`${e.members.length} more`:d.label,L=e.kind==="group"?12:h?10:8,N=o.w-L*2-(e.kind==="group"?46:0);if(n.fillText(Fs(n,A,N),o.x+L,o.y+o.h/2),e.kind==="group"){n.font=`${w*.85}px ui-monospace, Menlo, monospace`,n.fillStyle=i.textMuted,n.textAlign="right";let y=e.members.filter(x=>l.leaves.get(x)?.kind==="topic").length;c&&n.fillText(`${e.members.length-y}${y?` \xB7${y}\u229F`:""}`,o.x+o.w-10,o.y+o.h/2),n.textAlign="left"}n.textBaseline="alphabetic"}n.restore()}drawRail(e,s,t,n){let{ctx:i,theme:o}=this,a=this.camera.scale,l=s.attrs.lag??0,d=l>0?J(l,100,1e6):0;i.fillStyle=t,i.strokeStyle=o.border,i.lineWidth=1/Math.max(.5,Math.min(1,a)),pe(i,e.x,e.y,e.w,e.h,e.h/2),i.fill(),i.stroke();let c=Math.min(24,s.attrs.partitions??6);i.strokeStyle=o.border,i.globalAlpha=n*.6;for(let h=1;h<c;h++){let p=e.x+10+(e.w-20)*h/c;i.beginPath(),i.moveTo(p,e.y+3),i.lineTo(p,e.y+e.h-3),i.stroke()}if(d>.05){let h=(e.w-8)*d,p=d<.55?"#f59e0b":o.error;i.fillStyle=p,i.globalAlpha=n*(.35+d*.4),pe(i,e.x+4,e.y+4,h,e.h-8,(e.h-8)/2),i.fill(),i.globalAlpha=n*.9,i.fillRect(e.x+4+h-1.5,e.y+4,1.5,e.h-8)}i.globalAlpha=n,i.strokeStyle=o.textMuted,i.globalAlpha=n*.7,i.beginPath(),i.moveTo(e.x+6,e.y+2),i.lineTo(e.x+6,e.y+e.h-2),i.moveTo(e.x+e.w-6,e.y+2),i.lineTo(e.x+e.w-6,e.y+e.h-2),i.stroke(),i.globalAlpha=n}drawGlow(e,s,t){let{ctx:n,theme:i}=this,o=this.camera.scale,a=s>.5?(.5+.5*Math.sin(ge()/540+e.x*.013+e.y*.007))*(s-.5)*2:0,l=(3+s*s*26+a*5)/Math.max(.6,Math.min(1,o)),d=(.18+s*.62+a*.12)*t;n.save(),n.fillStyle=i.error;for(let[h,p]of[[1,.22],[.6,.3],[.3,.42]]){n.shadowColor=i.error,n.shadowBlur=l*h*2.2*o,n.globalAlpha=d*p*(i.name==="dark"?1:.8);let u=l*h*.5;pe(n,e.x-u,e.y-u,e.w+u*2,e.h+u*2,10+u),n.fill()}n.shadowBlur=0,n.strokeStyle=i.error,n.lineWidth=(1+s*s*3.5)/Math.max(.6,Math.min(1,o)),n.globalAlpha=(.4+s*.55)*t;let c=2+s*s*5;pe(n,e.x-c,e.y-c,e.w+c*2,e.h+c*2,10),n.stroke(),n.restore()}hatch(e,s){let{ctx:t,theme:n}=this;t.save(),pe(t,e.x,e.y,e.w,e.h,6),t.clip(),t.strokeStyle=n.error,t.globalAlpha=s*.55,t.lineWidth=1.5;for(let i=e.x-e.h;i<e.x+e.w;i+=7)t.beginPath(),t.moveTo(i,e.y+e.h),t.lineTo(i+e.h,e.y),t.stroke();t.restore()}rootOf(e){let s=this.model;e=Re(e);let t=s.leaves.get(e);if(t)return t.path[0]??e;let n=s.groups.get(e);for(;n?.parent;)n=s.groups.get(n.parent);return n?.id??e}colourOf(e){return this.groupColour.get(this.rootOf(e))??this.theme.edge}fillOf(e,s){let t=this.theme;if(this.lens==="ownership")return Ds(this.colourOf(e.id),t.name==="dark"?.72:.84,t);if(e.kind==="group")return e.id.endsWith("#rest")?t.name==="dark"?"#171d26":"#f3f5f8":t.name==="dark"?"#1d2531":"#eef2f7";let n=s;return s==="topic"?t.name==="dark"?"#2a2f3d":"#ecebf7":t.node[n]??t.node.service}edgeColour(e){let s=this.theme;switch(this.lens){case"ownership":return this.colourOf(e.from);case"latency":{let t=J(V(e.latencyMs,2,800),2,800);return Is(t,s.name==="dark")}case"reliability":{let t=e.errorRate>he?J(e.errorRate,he,Oe):0;return t>0?zt(s.edge,s.error,.3+t*.7):s.edge}case"blast":return e.rigid?s.name==="dark"?"#8fa3c0":"#4b5b73":s.edgeMuted;default:return e.async?s.flow.challenge:s.flow.request}}brighten(e){return zt(e,this.theme.name==="dark"?"#ffffff":"#000000",.25)}errorOf(e){let s=this.model;if(e.kind==="leaf")return s.leaves.get(e.id).errorRate;let t=0;for(let n of e.members)t=Math.max(t,s.leaves.get(n).errorRate);return t}hit(e,s){if(!this.layoutResult)return null;let t=this.toWorld(e,s);for(let o=this.units.length-1;o>=0;o--){let a=this.units[o],l=this.shapes.get(a.id);if(l&&t.x>=l.x&&t.x<=l.x+l.w&&t.y>=l.y&&t.y<=l.y+l.h)return{kind:"unit",id:a.id,x:e,y:s}}let n=5/this.camera.scale,i=null;for(let o of this.views){let a={x0:Math.min(o.a.x,o.b.x,o.c1.x,o.c2.x)-n,x1:Math.max(o.a.x,o.b.x,o.c1.x,o.c2.x)+n,y0:Math.min(o.a.y,o.b.y,o.c1.y,o.c2.y)-n,y1:Math.max(o.a.y,o.b.y,o.c1.y,o.c2.y)+n};if(!(t.x<a.x0||t.x>a.x1||t.y<a.y0||t.y>a.y1))for(let l=0;l<=24;l++){let d=an(o,l/24),c=Math.hypot(d.x-t.x,d.y-t.y);c<n+o.width/2&&(!i||c<i.d)&&(i={id:o.edge.id,d:c})}}if(i)return{kind:"edge",id:i.id,x:e,y:s};for(let o of[...this.layoutResult.bands].reverse()){let a=this.shapes.get(o.id)??o;if(t.x>=a.x&&t.x<=a.x+a.w&&t.y>=a.y&&t.y<=a.y+Math.min(a.h,26))return{kind:"band",id:o.id,x:e,y:s}}return null}zoomAt(e,s,t){this.cameraTo=null;let n=this.toWorld(e,s),i=this.fitScale*.55,o=V(this.camera.scale*t,i,3),a=ge();if(t>1){let d=this.hit(e,s),c=d?.kind==="unit"?this.unitById.get(d.id):void 0;if(c?.kind==="group"&&this.shapes.get(c.id).w*o>300&&a-this.lastSemantic>700){this.lastSemantic=a,this.expand(c.id);return}}else if(this.camera.scale<=this.fitScale*.9&&a-this.lastSemantic>700){let d=this.toWorld(e,s),c=[...this.layoutResult.bands].reverse().find(h=>{let p=this.shapes.get(h.id)??h;return d.x>=p.x&&d.x<=p.x+p.w&&d.y>=p.y&&d.y<=p.y+p.h});if(c){this.lastSemantic=a,this.collapse(c.id);return}}this.camera.scale=o;let l=this.toWorld(e,s);this.camera.x+=n.x-l.x,this.camera.y+=n.y-l.y,this.opts.onCamera?.()}updateTooltip(){let e=this.hovered;if(!e||!this.pointer||!this.model||this.drag){this.tooltip.style.opacity="0";return}let s=this.model,t="";if(e.kind==="unit"){let o=this.unitById.get(e.id);if(o.kind==="leaf"){let a=s.leaves.get(o.id),l=[a.id,`${a.kind} \xB7 ${a.path.join(" \u203A ")}`,`in ${Z(a.inRps)} out ${Z(a.outRps)}`];a.errorRate>0&&l.push(`errors ${(a.errorRate*100).toFixed(2)}%`),a.kind==="topic"&&l.push(`partitions ${a.attrs.partitions??"?"} lag ${Bn(a.attrs.lag??0)}`),a.attrs.instances&&l.push(`instances ${a.attrs.instances}`),this.health.dead.has(a.id)?l.push("DEAD"):this.health.degraded.has(a.id)&&l.push("degraded (a fail-open dependency is dead)"),t=l.join(`
|
|
11
|
+
`)}else if(!s.groups.has(o.id))t=`${o.members.length} more in ${s.groups.get(Re(o.id))?.label??"this group"}
|
|
12
|
+
not in the focus's neighbourhood \xB7 click to open all`;else{let a=s.groups.get(o.id),l=a.all.filter(p=>s.leaves.get(p).kind==="topic").length,d=a.all.filter(p=>this.health.dead.has(p)).length,c=[a.label,`${s.hierarchy[a.level]??"group"} \xB7 ${a.all.length-l} services${l?`, ${l} topics`:""}`,`traffic ${Z(a.rps)} internal ${Z(this.internal.get(a.id)??0)}`],h=this.errorOf(o);h>he&&c.push(`worst error rate ${(h*100).toFixed(1)}%`),a.lag>0&&c.push(`worst lag ${Bn(a.lag)}`),d&&c.push(`${d} dead`),c.push("click to open"),t=c.join(`
|
|
13
|
+
`)}}else if(e.kind==="edge"){let o=this.edges.find(a=>a.id===e.id);if(o){let a=[`${o.from} \u2192 ${o.to}`,`${Z(o.rps)} ${Pe(o.latencyMs)} ${(o.errorRate*100).toFixed(2)}% err`,o.async?"async (kafka)":o.mixed?"sync + async":"sync",o.rigid?"fail-closed on every flow":"has fail-open flows"];o.leafEdges.length>1?a.push(`${o.leafEdges.length} flows between ${o.fromLeaves} and ${o.toLeaves} services`):o.leafEdges[0]?.apis.length&&a.push(o.leafEdges[0].apis.slice(0,6).join(", ")),o.back&&a.push("against the grain (callback / cycle)"),t=a.join(`
|
|
14
|
+
`)}}else t=`${s.groups.get(e.id).label}
|
|
15
|
+
click header to collapse`;this.tooltip.textContent=t,this.tooltip.style.opacity="1";let n=Math.min(this.pointer.x+14,this.width-this.tooltip.offsetWidth-8),i=Math.min(this.pointer.y+14,this.height-this.tooltip.offsetHeight-8);this.tooltip.style.transform=`translate(${n}px, ${i}px)`}};function ge(){return typeof performance<"u"?performance.now():Date.now()}function on(r){return r<.5?4*r*r*r:1-Math.pow(-2*r+2,3)/2}function an(r,e){let s=1-e;return{x:s*s*s*r.a.x+3*s*s*e*r.c1.x+3*s*e*e*r.c2.x+e*e*e*r.b.x,y:s*s*s*r.a.y+3*s*s*e*r.c1.y+3*s*e*e*r.c2.y+e*e*e*r.b.y}}function pe(r,e,s,t,n,i){let o=Math.min(i,t/2,n/2);r.beginPath(),r.moveTo(e+o,s),r.arcTo(e+t,s,e+t,s+n,o),r.arcTo(e+t,s+n,e,s+n,o),r.arcTo(e,s+n,e,s,o),r.arcTo(e,s,e+t,s,o),r.closePath()}function Fs(r,e,s){if(r.measureText(e).width<=s)return e;let t=e;for(;t.length>2&&r.measureText(t+"\u2026").width>s;)t=t.slice(0,-1);return t+"\u2026"}function Bn(r){return r>=1e6?`${(r/1e6).toFixed(1)}M`:r>=1e3?`${(r/1e3).toFixed(1)}k`:`${Math.round(r)}`}function Hn(r){let e=r.replace("#",""),s=parseInt(e.length===3?e.split("").map(t=>t+t).join(""):e,16);return[s>>16&255,s>>8&255,s&255]}function zt(r,e,s){let[t,n,i]=Hn(r),[o,a,l]=Hn(e),d=(c,h)=>Math.round(c+(h-c)*s).toString(16).padStart(2,"0");return`#${d(t,o)}${d(n,a)}${d(i,l)}`}function Ds(r,e,s){return zt(r,s.name==="dark"?"#0e1117":"#ffffff",e)}function Is(r,e){let s=e?["#4ad6b8","#5aa9ff","#c58bff","#ffb54d","#ff6b6b"]:["#0d9488","#1f6feb","#8b5cf6","#d97706","#dc2626"],t=Math.min(s.length-2,Math.floor(r*(s.length-1)));return zt(s[t],s[t+1],r*(s.length-1)-t)}return Xn(js);})();
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tracelight data model.
|
|
3
|
+
*
|
|
4
|
+
* A `Graph` is a snapshot of a service topology plus the traffic measured on
|
|
5
|
+
* each edge over some window. Everything the renderer draws is derived from
|
|
6
|
+
* these numbers - there is no styling information in the data.
|
|
7
|
+
*/
|
|
8
|
+
/** What a node is, which decides its glyph and default colour. */
|
|
9
|
+
export type NodeKind = 'client' | 'gateway' | 'service' | 'datastore' | 'cache' | 'queue' | 'external';
|
|
10
|
+
/**
|
|
11
|
+
* What a flow is. This is deliberately semantic rather than visual: `retry`
|
|
12
|
+
* and `challenge` traffic is drawn as wasted work (dashed, muted, and counted
|
|
13
|
+
* separately in the summary) because that is the thing worth noticing.
|
|
14
|
+
*/
|
|
15
|
+
export type EdgeKind = 'request' | 'response' | 'retry' | 'challenge' | 'async';
|
|
16
|
+
export interface GraphNode {
|
|
17
|
+
id: string;
|
|
18
|
+
label?: string;
|
|
19
|
+
kind?: NodeKind;
|
|
20
|
+
/** Optional grouping key, e.g. a team or a bounded context. */
|
|
21
|
+
group?: string;
|
|
22
|
+
/** Pin the node to a layer (column). Omit to let the layout infer it. */
|
|
23
|
+
layer?: number;
|
|
24
|
+
/** Pin the node to explicit canvas coordinates, bypassing layout entirely. */
|
|
25
|
+
x?: number;
|
|
26
|
+
y?: number;
|
|
27
|
+
/** Anything you want back in the hover card. */
|
|
28
|
+
meta?: Record<string, unknown>;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Traffic measured on one edge.
|
|
32
|
+
*
|
|
33
|
+
* The visual encoding is fixed and intentional:
|
|
34
|
+
* rps -> particle emission rate (log-scaled; "how much")
|
|
35
|
+
* latencyMs -> particle speed (inverse; "how slow")
|
|
36
|
+
* errorRate -> fraction of red particles
|
|
37
|
+
* bytes -> particle radius (sqrt-scaled)
|
|
38
|
+
*
|
|
39
|
+
* Keeping throughput and latency in separate channels is the whole point: a
|
|
40
|
+
* saturated-but-fast link must not look like a quiet-but-slow one.
|
|
41
|
+
*/
|
|
42
|
+
export interface EdgeMetrics {
|
|
43
|
+
/** Requests per second. Drives particle density. Must be >= 0. */
|
|
44
|
+
rps: number;
|
|
45
|
+
/** Mean (or p50) latency in milliseconds. Drives particle speed. */
|
|
46
|
+
latencyMs?: number;
|
|
47
|
+
/** Fraction of failing calls, 0..1. Drives the red particle mix. */
|
|
48
|
+
errorRate?: number;
|
|
49
|
+
/** Mean payload size in bytes. Drives particle radius. */
|
|
50
|
+
bytes?: number;
|
|
51
|
+
}
|
|
52
|
+
export interface GraphEdge {
|
|
53
|
+
/** Stable id. Generated from `from->to` if omitted. */
|
|
54
|
+
id?: string;
|
|
55
|
+
from: string;
|
|
56
|
+
to: string;
|
|
57
|
+
label?: string;
|
|
58
|
+
kind?: EdgeKind;
|
|
59
|
+
metrics: EdgeMetrics;
|
|
60
|
+
meta?: Record<string, unknown>;
|
|
61
|
+
}
|
|
62
|
+
export interface GraphMeta {
|
|
63
|
+
title?: string;
|
|
64
|
+
/** Human description of the measurement window, e.g. "5m avg". */
|
|
65
|
+
window?: string;
|
|
66
|
+
generatedAt?: string;
|
|
67
|
+
[key: string]: unknown;
|
|
68
|
+
}
|
|
69
|
+
export interface Graph {
|
|
70
|
+
/** Schema version. Currently always 1. */
|
|
71
|
+
version?: 1;
|
|
72
|
+
nodes: GraphNode[];
|
|
73
|
+
edges: GraphEdge[];
|
|
74
|
+
meta?: GraphMeta;
|
|
75
|
+
}
|
|
76
|
+
/** A graph after validation and defaulting - what the renderer actually uses. */
|
|
77
|
+
export interface ResolvedEdge extends GraphEdge {
|
|
78
|
+
id: string;
|
|
79
|
+
kind: EdgeKind;
|
|
80
|
+
metrics: Required<EdgeMetrics>;
|
|
81
|
+
}
|
|
82
|
+
export interface ResolvedNode extends GraphNode {
|
|
83
|
+
label: string;
|
|
84
|
+
kind: NodeKind;
|
|
85
|
+
}
|
|
86
|
+
export interface ResolvedGraph {
|
|
87
|
+
version: 1;
|
|
88
|
+
nodes: ResolvedNode[];
|
|
89
|
+
edges: ResolvedEdge[];
|
|
90
|
+
meta: GraphMeta;
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,kEAAkE;AAClE,MAAM,MAAM,QAAQ,GAChB,QAAQ,GACR,SAAS,GACT,SAAS,GACT,WAAW,GACX,OAAO,GACP,OAAO,GACP,UAAU,CAAC;AAEf;;;;GAIG;AACH,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,UAAU,GAAG,OAAO,GAAG,WAAW,GAAG,OAAO,CAAC;AAEhF,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,+DAA+D;IAC/D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yEAAyE;IACzE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8EAA8E;IAC9E,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,gDAAgD;IAChD,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,WAAW;IAC1B,kEAAkE;IAClE,GAAG,EAAE,MAAM,CAAC;IACZ,oEAAoE;IACpE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oEAAoE;IACpE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0DAA0D;IAC1D,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,SAAS;IACxB,uDAAuD;IACvD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,OAAO,EAAE,WAAW,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,KAAK;IACpB,0CAA0C;IAC1C,OAAO,CAAC,EAAE,CAAC,CAAC;IACZ,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,IAAI,CAAC,EAAE,SAAS,CAAC;CAClB;AAED,iFAAiF;AACjF,MAAM,WAAW,YAAa,SAAQ,SAAS;IAC7C,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,QAAQ,CAAC;IACf,OAAO,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,YAAa,SAAQ,SAAS;IAC7C,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,QAAQ,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,CAAC,CAAC;IACX,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,IAAI,EAAE,SAAS,CAAC;CACjB"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tracelight data model.
|
|
3
|
+
*
|
|
4
|
+
* A `Graph` is a snapshot of a service topology plus the traffic measured on
|
|
5
|
+
* each edge over some window. Everything the renderer draws is derived from
|
|
6
|
+
* these numbers - there is no styling information in the data.
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
|
package/package.json
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "trace-viz",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Visualize service topologies as living, re-projectable pictures. Every dimension is a knob: choose what a box is, bind any dimension to any visual channel, and drop into a 3D world where fail-closed dependencies carry blast radius.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"3d",
|
|
7
|
+
"blast-radius",
|
|
8
|
+
"canvas",
|
|
9
|
+
"dependency-graph",
|
|
10
|
+
"distributed-systems",
|
|
11
|
+
"grammar-of-graphics",
|
|
12
|
+
"latency",
|
|
13
|
+
"observability",
|
|
14
|
+
"particles",
|
|
15
|
+
"service-map",
|
|
16
|
+
"service-mesh",
|
|
17
|
+
"throughput",
|
|
18
|
+
"topology",
|
|
19
|
+
"trace-viz",
|
|
20
|
+
"visualization"
|
|
21
|
+
],
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"type": "module",
|
|
24
|
+
"sideEffects": false,
|
|
25
|
+
"main": "./dist/index.js",
|
|
26
|
+
"module": "./dist/index.js",
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"default": "./dist/index.js"
|
|
32
|
+
},
|
|
33
|
+
"./react": {
|
|
34
|
+
"types": "./dist/react/index.d.ts",
|
|
35
|
+
"default": "./dist/react/index.js"
|
|
36
|
+
},
|
|
37
|
+
"./seed": {
|
|
38
|
+
"types": "./dist/seed.d.ts",
|
|
39
|
+
"default": "./dist/seed.js"
|
|
40
|
+
},
|
|
41
|
+
"./package.json": "./package.json",
|
|
42
|
+
"./agent": {
|
|
43
|
+
"types": "./dist/agent.d.ts",
|
|
44
|
+
"default": "./dist/agent.js"
|
|
45
|
+
},
|
|
46
|
+
"./mesh": {
|
|
47
|
+
"types": "./dist/mesh.d.ts",
|
|
48
|
+
"default": "./dist/mesh.js"
|
|
49
|
+
},
|
|
50
|
+
"./atlas": {
|
|
51
|
+
"types": "./dist/atlas/index.d.ts",
|
|
52
|
+
"default": "./dist/atlas/index.js"
|
|
53
|
+
},
|
|
54
|
+
"./estate": {
|
|
55
|
+
"types": "./dist/estate.d.ts",
|
|
56
|
+
"default": "./dist/estate.js"
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"files": [
|
|
60
|
+
"dist",
|
|
61
|
+
"README.md",
|
|
62
|
+
"LICENSE",
|
|
63
|
+
"CHANGELOG.md"
|
|
64
|
+
],
|
|
65
|
+
"scripts": {
|
|
66
|
+
"build": "npm run build:types && npm run build:bundle",
|
|
67
|
+
"build:types": "tsc -p tsconfig.build.json",
|
|
68
|
+
"build:bundle": "node scripts/bundle.mjs",
|
|
69
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
70
|
+
"test": "node --test test/*.test.mjs",
|
|
71
|
+
"demo": "npm run build:bundle && node scripts/serve.mjs",
|
|
72
|
+
"prepublishOnly": "npm run build",
|
|
73
|
+
"screenshots": "node scripts/screenshots.mjs",
|
|
74
|
+
"artifact": "node scripts/build-artifact.mjs && node scripts/build-artifact-mesh.mjs",
|
|
75
|
+
"launch": "bash scripts/launch.sh"
|
|
76
|
+
},
|
|
77
|
+
"peerDependencies": {
|
|
78
|
+
"react": ">=17"
|
|
79
|
+
},
|
|
80
|
+
"peerDependenciesMeta": {
|
|
81
|
+
"react": {
|
|
82
|
+
"optional": true
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
"devDependencies": {
|
|
86
|
+
"esbuild": "^0.25.0",
|
|
87
|
+
"typescript": "^5.6.0",
|
|
88
|
+
"@types/react": "^18.3.0",
|
|
89
|
+
"playwright-core": "^1.48.0"
|
|
90
|
+
},
|
|
91
|
+
"engines": {
|
|
92
|
+
"node": ">=18"
|
|
93
|
+
},
|
|
94
|
+
"repository": {
|
|
95
|
+
"type": "git",
|
|
96
|
+
"url": "git+https://github.com/salujayatharth/trace-viz.git"
|
|
97
|
+
},
|
|
98
|
+
"homepage": "https://salujayatharth.github.io/trace-viz/",
|
|
99
|
+
"bugs": {
|
|
100
|
+
"url": "https://github.com/salujayatharth/trace-viz/issues"
|
|
101
|
+
},
|
|
102
|
+
"publishConfig": {
|
|
103
|
+
"access": "public"
|
|
104
|
+
}
|
|
105
|
+
}
|