Skip to content

Commit 60d367d

Browse files
lesnik512claude
andcommitted
feat(demos): add full-stack macro strip; finalize change summary
Reuse renderRateStrip on the full-stack page: a small backend call-rate strip below the lanes, revealed each tick with the single-client run, labeled with the active incident phase. Gated by config.macroStrip so every other lane page is unaffected. Finalizes the change summary with the realized herd multipliers (naive ~70x vs httpware ~3x). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent e8e9f63 commit 60d367d

4 files changed

Lines changed: 30 additions & 1 deletion

File tree

‎docs/demos/demos.css‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,3 +158,9 @@
158158
clip-path: polygon(50% 0, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%);
159159
width: 11px; height: 11px; top: 4px;
160160
}
161+
162+
/* full-stack macro strip (reuses .herd-strip / .herd-bar-hw) */
163+
.hw-demo .macro { margin-top: 6px; }
164+
.hw-demo .macro-head { font-family: var(--hw-mono); font-size: .72rem; color: var(--hw-muted); margin-bottom: 4px; }
165+
.hw-demo .macro .herd-strip { height: 54px; }
166+
.hw-demo .macro .herd-svg { height: 54px; }

‎docs/demos/engine.js‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,11 @@ window.HttpwareDemo = (function () {
524524
</div>
525525
526526
<p class="note" data-el="note">Faithful model of httpware, not httpware running in your browser. Pick a scenario above to begin.</p>
527+
528+
<div class="macro" data-el="macroWrap" style="display:none">
529+
<div class="macro-head"><span class="k">backend call-rate</span> &mdash; <span data-el="macroStage">healthy</span></div>
530+
<div class="herd-strip" data-el="macroStrip"></div>
531+
</div>
527532
</div>
528533
529534
<div class="dim" data-el="dimT"></div><div class="dim" data-el="dimB"></div>
@@ -554,6 +559,7 @@ window.HttpwareDemo = (function () {
554559
poolWrap: $('poolWrap'), poolB: $('poolB'),
555560
elapsedWrap: $('elapsedWrap'), elapsedB: $('elapsedB'),
556561
note: $('note'),
562+
macroWrap: $('macroWrap'), macroStrip: $('macroStrip'), macroStage: $('macroStage'),
557563
dimT: $('dimT'), dimB: $('dimB'), dimL: $('dimL'), dimR: $('dimR'), ring: $('ring'),
558564
coach: $('coach'), cArrow: $('cArrow'), cStep: $('cStep'), cTitle: $('cTitle'), cBody: $('cBody'), cGo: $('cGo'),
559565
};
@@ -579,6 +585,7 @@ window.HttpwareDemo = (function () {
579585
let A = { ok: 0, bad: 0, if: 0, pend: [] };
580586
let B = { ok: 0, bad: 0, rej: 0, if: 0, pend: [] };
581587
let brk = null;
588+
let macroSeries = []; // lane-B backend calls per tick, for config.macroStrip
582589
let bulk = null;
583590
let retryCfg = null, budget = null, budgetExhausted = false;
584591
let tmoCfg = null, timedOutCount = 0;
@@ -641,6 +648,7 @@ window.HttpwareDemo = (function () {
641648
els.poolB.textContent = cfgB.bulkhead ? ('0/' + cfgB.bulkhead.maxConcurrent) : '—';
642649
els.elapsedWrap.style.display = cfgB.timeout ? '' : 'none';
643650
els.elapsedB.textContent = cfgB.timeout ? ('0.0s / ' + cfgB.timeout.timeout.toFixed(1) + 's') : '—';
651+
els.macroWrap.style.display = (selectedScenario && config.macroStrip) ? '' : 'none';
644652
els.srvA.className = 'box'; els.srvA.textContent = 'server ✓';
645653
els.srvB.className = 'box'; els.srvB.textContent = 'server ✓';
646654
els.laneA.className = 'lane'; els.laneB.className = 'lane';
@@ -729,6 +737,8 @@ window.HttpwareDemo = (function () {
729737
timedOutCount = 0;
730738
rnd = mulberry(SEED);
731739
resetVisual();
740+
macroSeries = new Array(Math.ceil(scenario.dur * 1000 / TICK) + 2).fill(0);
741+
if (config.macroStrip) els.macroWrap.style.display = '';
732742

733743
timer = setInterval(() => {
734744
if (paused) return;
@@ -776,6 +786,7 @@ window.HttpwareDemo = (function () {
776786
L.pend.push({ land, ok: f2.ok, attempt: nextAttempt, bh: p.bh, start: p.start, deadline: p.deadline, timedOut });
777787
spawnDot(dotsB, els.trackB, f2.ok ? 'ok' : 'bad');
778788
retried = true;
789+
if (config.macroStrip) macroSeries[tick]++;
779790
} else if (budget) {
780791
budgetExhausted = true;
781792
}
@@ -822,9 +833,16 @@ window.HttpwareDemo = (function () {
822833
const { land, timedOut } = clampToDeadline(tick, landTicks, deadline);
823834
B.if++; B.pend.push({ land, ok: f.ok, attempt: 0, bh: !!bulk, start: now, deadline, timedOut });
824835
spawnDot(dotsB, els.trackB, f.ok ? 'ok' : 'bad');
836+
if (config.macroStrip) macroSeries[tick]++;
825837
}
826838
}
827839
updateUI(now, lastFault);
840+
if (config.macroStrip) {
841+
const peak = macroSeries.reduce((m, v) => Math.max(m, v), 0) || 1;
842+
renderRateStrip(els.macroStrip, macroSeries, tick + 1,
843+
{ peakScale: peak, baseline: 0, outage: null, dur: scenario.dur * 1000 / TICK, cls: 'herd-bar-hw' });
844+
els.macroStage.textContent = config.stageLabel ? config.stageLabel(now) : '';
845+
}
828846
tick++;
829847
if (now >= scenario.dur) clearInterval(timer);
830848
}, TICK);

‎docs/demos/full-stack.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ document.addEventListener('DOMContentLoaded', function () {
2424
retry: { maxAttempts: 3, baseDelay: 0.1, maxDelay: 5.0 },
2525
budget: { ttl: 10.0, minRetriesPerSec: 10.0, percentCanRetry: 0.2 } } },
2626
],
27+
macroStrip: true,
28+
stageLabel: (now) => now < 2 ? 'healthy'
29+
: now < 5 ? 'phase 1 — latency spike'
30+
: now < 8 ? 'phase 2 — brownout'
31+
: now < 12 ? 'phase 3 — hard down' : 'recovered',
2732
buildStops: () => [
2833
{ when: (s) => s.now >= 2.4, spot: ['poolB', 'elapsedB'], title: 'Phase 1 — latency spike',
2934
body: 'Bulkhead caps concurrency so the slow phase can’t exhaust the client; timeout bounds each operation at 2s.' },

‎planning/changes/2026-07-19.02-resilience-demo-herd-and-enrich.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
summary: Add a many-client thundering-herd macro view to the retry/budget demo and fold borrowed visualization techniques (backoff countdown ring, jagged failure shape, full-stack macro strip) across the demo suite.
2+
summary: Added a many-client thundering-herd macro view to the retry page (naive ~70x vs httpware ~3x peak backend load, httpware bounded by max_attempts at demo scale with a per-client budget) and folded three enrich techniques across the demo suite — a backoff countdown ring on timeout + circuit-breaker only (retry's sub-tick backoff makes a faithful ring there impossible, so it's dropped, not faked), a color-independent jagged failure shape everywhere, and a live backend call-rate macro strip with active-phase label on the full-stack page.
33
---
44

55
# Design: Resilience demo herd view + enrich pass

0 commit comments

Comments
 (0)