added docs

This commit is contained in:
buenosairesam
2025-12-31 12:45:28 -03:00
parent 040fccc58d
commit b526bde98e
19 changed files with 1382 additions and 276 deletions

View File

@@ -0,0 +1,106 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 400">
<defs>
<linearGradient id="headerGrad" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:#16213e"/>
<stop offset="100%" style="stop-color:#533483"/>
</linearGradient>
<marker id="arrowhead" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#e94560"/>
</marker>
<marker id="arrowhead-gray" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#64748b"/>
</marker>
</defs>
<!-- Background -->
<rect width="800" height="400" fill="#1a1a2e"/>
<!-- Title -->
<text x="400" y="30" text-anchor="middle" fill="#e94560" font-family="system-ui" font-size="18" font-weight="bold">System Architecture Overview</text>
<!-- Machine boxes (left side) -->
<g transform="translate(50, 70)">
<rect width="120" height="60" rx="8" fill="#0f3460" stroke="#e94560" stroke-width="2"/>
<text x="60" y="25" text-anchor="middle" fill="#eee" font-family="system-ui" font-size="12" font-weight="bold">Machine 1</text>
<text x="60" y="42" text-anchor="middle" fill="#a0a0a0" font-family="system-ui" font-size="10">Collector</text>
</g>
<g transform="translate(50, 150)">
<rect width="120" height="60" rx="8" fill="#0f3460" stroke="#e94560" stroke-width="2"/>
<text x="60" y="25" text-anchor="middle" fill="#eee" font-family="system-ui" font-size="12" font-weight="bold">Machine 2</text>
<text x="60" y="42" text-anchor="middle" fill="#a0a0a0" font-family="system-ui" font-size="10">Collector</text>
</g>
<g transform="translate(50, 230)">
<rect width="120" height="60" rx="8" fill="#0f3460" stroke="#e94560" stroke-width="2"/>
<text x="60" y="25" text-anchor="middle" fill="#eee" font-family="system-ui" font-size="12" font-weight="bold">Machine N</text>
<text x="60" y="42" text-anchor="middle" fill="#a0a0a0" font-family="system-ui" font-size="10">Collector</text>
</g>
<!-- Aggregator (center) -->
<g transform="translate(280, 130)">
<rect width="140" height="80" rx="8" fill="#16213e" stroke="#e94560" stroke-width="2"/>
<text x="70" y="30" text-anchor="middle" fill="#e94560" font-family="system-ui" font-size="14" font-weight="bold">Aggregator</text>
<text x="70" y="50" text-anchor="middle" fill="#a0a0a0" font-family="system-ui" font-size="10">gRPC Server</text>
<text x="70" y="65" text-anchor="middle" fill="#a0a0a0" font-family="system-ui" font-size="10">Batch Processing</text>
</g>
<!-- Storage (bottom center) -->
<g transform="translate(250, 300)">
<rect width="80" height="50" rx="6" fill="#0f3460" stroke="#64748b" stroke-width="1"/>
<text x="40" y="22" text-anchor="middle" fill="#eee" font-family="system-ui" font-size="11" font-weight="bold">Redis</text>
<text x="40" y="38" text-anchor="middle" fill="#a0a0a0" font-family="system-ui" font-size="9">Hot Data</text>
</g>
<g transform="translate(350, 300)">
<rect width="100" height="50" rx="6" fill="#0f3460" stroke="#64748b" stroke-width="1"/>
<text x="50" y="22" text-anchor="middle" fill="#eee" font-family="system-ui" font-size="11" font-weight="bold">TimescaleDB</text>
<text x="50" y="38" text-anchor="middle" fill="#a0a0a0" font-family="system-ui" font-size="9">Historical</text>
</g>
<!-- Gateway (right) -->
<g transform="translate(520, 130)">
<rect width="120" height="80" rx="8" fill="#16213e" stroke="#e94560" stroke-width="2"/>
<text x="60" y="30" text-anchor="middle" fill="#e94560" font-family="system-ui" font-size="14" font-weight="bold">Gateway</text>
<text x="60" y="50" text-anchor="middle" fill="#a0a0a0" font-family="system-ui" font-size="10">FastAPI</text>
<text x="60" y="65" text-anchor="middle" fill="#a0a0a0" font-family="system-ui" font-size="10">WebSocket</text>
</g>
<!-- Browser (far right) -->
<g transform="translate(700, 140)">
<rect width="80" height="60" rx="8" fill="#0f3460" stroke="#4ade80" stroke-width="2"/>
<text x="40" y="25" text-anchor="middle" fill="#eee" font-family="system-ui" font-size="11" font-weight="bold">Browser</text>
<text x="40" y="42" text-anchor="middle" fill="#a0a0a0" font-family="system-ui" font-size="9">Dashboard</text>
</g>
<!-- Alerts (bottom right) -->
<g transform="translate(540, 300)">
<rect width="100" height="50" rx="6" fill="#0f3460" stroke="#fbbf24" stroke-width="1"/>
<text x="50" y="22" text-anchor="middle" fill="#eee" font-family="system-ui" font-size="11" font-weight="bold">Alerts</text>
<text x="50" y="38" text-anchor="middle" fill="#a0a0a0" font-family="system-ui" font-size="9">Threshold Rules</text>
</g>
<!-- Arrows: Collectors to Aggregator -->
<line x1="170" y1="100" x2="275" y2="160" stroke="#e94560" stroke-width="2" marker-end="url(#arrowhead)"/>
<line x1="170" y1="180" x2="275" y2="170" stroke="#e94560" stroke-width="2" marker-end="url(#arrowhead)"/>
<line x1="170" y1="260" x2="275" y2="185" stroke="#e94560" stroke-width="2" marker-end="url(#arrowhead)"/>
<!-- Arrow: Aggregator to Storage -->
<line x1="320" y1="210" x2="300" y2="295" stroke="#64748b" stroke-width="1.5" marker-end="url(#arrowhead-gray)"/>
<line x1="380" y1="210" x2="400" y2="295" stroke="#64748b" stroke-width="1.5" marker-end="url(#arrowhead-gray)"/>
<!-- Arrow: Aggregator to Gateway -->
<line x1="420" y1="170" x2="515" y2="170" stroke="#e94560" stroke-width="2" marker-end="url(#arrowhead)"/>
<!-- Arrow: Gateway to Browser -->
<line x1="640" y1="170" x2="695" y2="170" stroke="#4ade80" stroke-width="2" marker-end="url(#arrowhead)"/>
<!-- Arrow: Event stream to Alerts -->
<path d="M 400 210 Q 450 260 535 325" stroke="#fbbf24" stroke-width="1.5" fill="none" marker-end="url(#arrowhead)"/>
<!-- Labels for arrows -->
<text x="220" y="125" fill="#e94560" font-family="system-ui" font-size="9">gRPC Stream</text>
<text x="460" y="155" fill="#e94560" font-family="system-ui" font-size="9">Events</text>
<text x="660" y="155" fill="#4ade80" font-family="system-ui" font-size="9">WS</text>
<text x="470" y="280" fill="#fbbf24" font-family="system-ui" font-size="9">Pub/Sub</text>
</svg>

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

@@ -0,0 +1,83 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 700 300">
<defs>
<marker id="arr" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#e94560"/>
</marker>
</defs>
<!-- Background -->
<rect width="700" height="300" fill="#1a1a2e"/>
<!-- Title -->
<text x="350" y="30" text-anchor="middle" fill="#e94560" font-family="system-ui" font-size="16" font-weight="bold">gRPC Client-Side Streaming</text>
<!-- Collector box -->
<g transform="translate(50, 80)">
<rect width="180" height="160" rx="8" fill="#16213e" stroke="#e94560" stroke-width="2"/>
<text x="90" y="25" text-anchor="middle" fill="#e94560" font-family="system-ui" font-size="13" font-weight="bold">Collector</text>
<!-- Metric items -->
<g transform="translate(15, 45)">
<rect width="150" height="25" rx="4" fill="#0f3460"/>
<text x="10" y="17" fill="#a0a0a0" font-family="monospace" font-size="10">CPU: 45%</text>
</g>
<g transform="translate(15, 75)">
<rect width="150" height="25" rx="4" fill="#0f3460"/>
<text x="10" y="17" fill="#a0a0a0" font-family="monospace" font-size="10">Memory: 62%</text>
</g>
<g transform="translate(15, 105)">
<rect width="150" height="25" rx="4" fill="#0f3460"/>
<text x="10" y="17" fill="#a0a0a0" font-family="monospace" font-size="10">Disk: 78%</text>
</g>
</g>
<!-- Stream visualization -->
<g transform="translate(250, 100)">
<!-- Stream line -->
<line x1="0" y1="60" x2="180" y2="60" stroke="#e94560" stroke-width="3" stroke-dasharray="8,4"/>
<!-- Metric packets -->
<g transform="translate(20, 45)">
<rect width="30" height="30" rx="4" fill="#e94560"/>
<text x="15" y="20" text-anchor="middle" fill="#fff" font-family="monospace" font-size="10">M1</text>
</g>
<g transform="translate(70, 45)">
<rect width="30" height="30" rx="4" fill="#e94560" opacity="0.8"/>
<text x="15" y="20" text-anchor="middle" fill="#fff" font-family="monospace" font-size="10">M2</text>
</g>
<g transform="translate(120, 45)">
<rect width="30" height="30" rx="4" fill="#e94560" opacity="0.6"/>
<text x="15" y="20" text-anchor="middle" fill="#fff" font-family="monospace" font-size="10">M3</text>
</g>
<text x="90" y="110" text-anchor="middle" fill="#a0a0a0" font-family="system-ui" font-size="10">Continuous stream of metrics</text>
</g>
<!-- Aggregator box -->
<g transform="translate(450, 80)">
<rect width="180" height="160" rx="8" fill="#16213e" stroke="#e94560" stroke-width="2"/>
<text x="90" y="25" text-anchor="middle" fill="#e94560" font-family="system-ui" font-size="13" font-weight="bold">Aggregator</text>
<!-- Batch indicator -->
<g transform="translate(15, 45)">
<rect width="150" height="40" rx="4" fill="#0f3460"/>
<text x="75" y="17" text-anchor="middle" fill="#eee" font-family="system-ui" font-size="10">Batch: 20 metrics</text>
<text x="75" y="32" text-anchor="middle" fill="#4ade80" font-family="system-ui" font-size="9">Flush to storage</text>
</g>
<!-- Storage icons -->
<g transform="translate(15, 100)">
<rect width="65" height="30" rx="4" fill="#0f3460"/>
<text x="32" y="20" text-anchor="middle" fill="#a0a0a0" font-family="system-ui" font-size="9">Redis</text>
</g>
<g transform="translate(95, 100)">
<rect width="70" height="30" rx="4" fill="#0f3460"/>
<text x="35" y="20" text-anchor="middle" fill="#a0a0a0" font-family="system-ui" font-size="9">Timescale</text>
</g>
</g>
<!-- Legend -->
<g transform="translate(50, 260)">
<text x="0" y="0" fill="#a0a0a0" font-family="system-ui" font-size="11">One persistent connection. Metrics flow continuously. No polling overhead.</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -0,0 +1,83 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 700 320">
<defs>
<linearGradient id="hotGrad" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:#ef4444"/>
<stop offset="100%" style="stop-color:#f97316"/>
</linearGradient>
<linearGradient id="warmGrad" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:#f97316"/>
<stop offset="100%" style="stop-color:#eab308"/>
</linearGradient>
<linearGradient id="coldGrad" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:#3b82f6"/>
<stop offset="100%" style="stop-color:#6366f1"/>
</linearGradient>
</defs>
<!-- Background -->
<rect width="700" height="320" fill="#1a1a2e"/>
<!-- Title -->
<text x="350" y="30" text-anchor="middle" fill="#e94560" font-family="system-ui" font-size="16" font-weight="bold">Tiered Storage Architecture</text>
<!-- Hot tier -->
<g transform="translate(50, 60)">
<rect width="180" height="100" rx="8" fill="#16213e" stroke="url(#hotGrad)" stroke-width="3"/>
<rect x="10" y="10" width="30" height="10" rx="2" fill="url(#hotGrad)"/>
<text x="50" y="18" fill="#ef4444" font-family="system-ui" font-size="11" font-weight="bold">HOT</text>
<text x="90" y="45" text-anchor="middle" fill="#eee" font-family="system-ui" font-size="14" font-weight="bold">Redis</text>
<text x="90" y="65" text-anchor="middle" fill="#a0a0a0" font-family="system-ui" font-size="10">5 second resolution</text>
<text x="90" y="80" text-anchor="middle" fill="#a0a0a0" font-family="system-ui" font-size="10">5 minute retention</text>
</g>
<!-- Warm tier - Raw -->
<g transform="translate(260, 60)">
<rect width="180" height="100" rx="8" fill="#16213e" stroke="url(#warmGrad)" stroke-width="3"/>
<rect x="10" y="10" width="40" height="10" rx="2" fill="url(#warmGrad)"/>
<text x="58" y="18" fill="#f97316" font-family="system-ui" font-size="11" font-weight="bold">WARM</text>
<text x="90" y="45" text-anchor="middle" fill="#eee" font-family="system-ui" font-size="14" font-weight="bold">TimescaleDB</text>
<text x="90" y="65" text-anchor="middle" fill="#a0a0a0" font-family="system-ui" font-size="10">5 second resolution</text>
<text x="90" y="80" text-anchor="middle" fill="#a0a0a0" font-family="system-ui" font-size="10">24 hour retention</text>
</g>
<!-- Cold tier - Aggregates -->
<g transform="translate(470, 60)">
<rect width="180" height="100" rx="8" fill="#16213e" stroke="url(#coldGrad)" stroke-width="3"/>
<rect x="10" y="10" width="40" height="10" rx="2" fill="url(#coldGrad)"/>
<text x="58" y="18" fill="#3b82f6" font-family="system-ui" font-size="11" font-weight="bold">COLD</text>
<text x="90" y="45" text-anchor="middle" fill="#eee" font-family="system-ui" font-size="14" font-weight="bold">Aggregates</text>
<text x="90" y="65" text-anchor="middle" fill="#a0a0a0" font-family="system-ui" font-size="10">1 min / 1 hour resolution</text>
<text x="90" y="80" text-anchor="middle" fill="#a0a0a0" font-family="system-ui" font-size="10">7 / 90 day retention</text>
</g>
<!-- Use cases -->
<g transform="translate(50, 180)">
<rect width="180" height="60" rx="6" fill="#0f3460"/>
<text x="90" y="20" text-anchor="middle" fill="#ef4444" font-family="system-ui" font-size="11" font-weight="bold">Dashboard</text>
<text x="90" y="38" text-anchor="middle" fill="#a0a0a0" font-family="system-ui" font-size="10">"What's the CPU now?"</text>
<text x="90" y="52" text-anchor="middle" fill="#4ade80" font-family="system-ui" font-size="9">Fast in-memory reads</text>
</g>
<g transform="translate(260, 180)">
<rect width="180" height="60" rx="6" fill="#0f3460"/>
<text x="90" y="20" text-anchor="middle" fill="#f97316" font-family="system-ui" font-size="11" font-weight="bold">Recent Graphs</text>
<text x="90" y="38" text-anchor="middle" fill="#a0a0a0" font-family="system-ui" font-size="10">"Last hour of metrics"</text>
<text x="90" y="52" text-anchor="middle" fill="#4ade80" font-family="system-ui" font-size="9">Hypertable queries</text>
</g>
<g transform="translate(470, 180)">
<rect width="180" height="60" rx="6" fill="#0f3460"/>
<text x="90" y="20" text-anchor="middle" fill="#3b82f6" font-family="system-ui" font-size="11" font-weight="bold">Trends / Reports</text>
<text x="90" y="38" text-anchor="middle" fill="#a0a0a0" font-family="system-ui" font-size="10">"Weekly CPU average"</text>
<text x="90" y="52" text-anchor="middle" fill="#4ade80" font-family="system-ui" font-size="9">Pre-aggregated data</text>
</g>
<!-- Arrow showing flow -->
<g transform="translate(50, 260)">
<line x1="90" y1="15" x2="560" y2="15" stroke="#64748b" stroke-width="2"/>
<polygon points="560,10 580,15 560,20" fill="#64748b"/>
<text x="90" y="40" fill="#ef4444" font-family="system-ui" font-size="10">Fastest</text>
<text x="320" y="40" text-anchor="middle" fill="#a0a0a0" font-family="system-ui" font-size="10">Access Time</text>
<text x="550" y="40" text-anchor="end" fill="#3b82f6" font-family="system-ui" font-size="10">Slowest</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@@ -0,0 +1,84 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 700 280">
<defs>
<marker id="arrPink" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
<polygon points="0 0, 8 3, 0 6" fill="#e94560"/>
</marker>
<marker id="arrYellow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
<polygon points="0 0, 8 3, 0 6" fill="#fbbf24"/>
</marker>
<marker id="arrGreen" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
<polygon points="0 0, 8 3, 0 6" fill="#4ade80"/>
</marker>
</defs>
<!-- Background -->
<rect width="700" height="280" fill="#1a1a2e"/>
<!-- Title -->
<text x="350" y="28" text-anchor="middle" fill="#e94560" font-family="system-ui" font-size="16" font-weight="bold">Event-Driven Architecture</text>
<!-- Aggregator (publisher) -->
<g transform="translate(50, 70)">
<rect width="120" height="70" rx="8" fill="#16213e" stroke="#e94560" stroke-width="2"/>
<text x="60" y="30" text-anchor="middle" fill="#e94560" font-family="system-ui" font-size="12" font-weight="bold">Aggregator</text>
<text x="60" y="50" text-anchor="middle" fill="#a0a0a0" font-family="system-ui" font-size="10">Publisher</text>
</g>
<!-- Event Stream (center) -->
<g transform="translate(230, 55)">
<rect width="200" height="100" rx="8" fill="#0f3460" stroke="#fbbf24" stroke-width="2"/>
<text x="100" y="25" text-anchor="middle" fill="#fbbf24" font-family="system-ui" font-size="13" font-weight="bold">Event Stream</text>
<text x="100" y="45" text-anchor="middle" fill="#a0a0a0" font-family="system-ui" font-size="10">Redis Pub/Sub</text>
<!-- Topics -->
<g transform="translate(15, 55)">
<rect width="80" height="22" rx="4" fill="#16213e"/>
<text x="40" y="15" text-anchor="middle" fill="#e94560" font-family="monospace" font-size="9">metrics.raw</text>
</g>
<g transform="translate(105, 55)">
<rect width="80" height="22" rx="4" fill="#16213e"/>
<text x="40" y="15" text-anchor="middle" fill="#fbbf24" font-family="monospace" font-size="9">alerts.*</text>
</g>
</g>
<!-- Subscribers -->
<g transform="translate(490, 50)">
<rect width="130" height="50" rx="6" fill="#16213e" stroke="#4ade80" stroke-width="2"/>
<text x="65" y="22" text-anchor="middle" fill="#4ade80" font-family="system-ui" font-size="11" font-weight="bold">Gateway</text>
<text x="65" y="38" text-anchor="middle" fill="#a0a0a0" font-family="system-ui" font-size="9">WebSocket push</text>
</g>
<g transform="translate(490, 115)">
<rect width="130" height="50" rx="6" fill="#16213e" stroke="#fbbf24" stroke-width="2"/>
<text x="65" y="22" text-anchor="middle" fill="#fbbf24" font-family="system-ui" font-size="11" font-weight="bold">Alerts</text>
<text x="65" y="38" text-anchor="middle" fill="#a0a0a0" font-family="system-ui" font-size="9">Threshold check</text>
</g>
<!-- Arrows -->
<line x1="170" y1="105" x2="225" y2="105" stroke="#e94560" stroke-width="2" marker-end="url(#arrPink)"/>
<line x1="430" y1="85" x2="485" y2="75" stroke="#4ade80" stroke-width="2" marker-end="url(#arrGreen)"/>
<line x1="430" y1="115" x2="485" y2="140" stroke="#fbbf24" stroke-width="2" marker-end="url(#arrYellow)"/>
<!-- Benefits box -->
<g transform="translate(50, 180)">
<rect width="600" height="80" rx="8" fill="#0f3460"/>
<text x="20" y="25" fill="#eee" font-family="system-ui" font-size="12" font-weight="bold">Benefits:</text>
<g transform="translate(20, 40)">
<circle cx="6" cy="6" r="4" fill="#4ade80"/>
<text x="16" y="10" fill="#a0a0a0" font-family="system-ui" font-size="10">Decoupled services - can restart independently</text>
</g>
<g transform="translate(20, 58)">
<circle cx="6" cy="6" r="4" fill="#4ade80"/>
<text x="16" y="10" fill="#a0a0a0" font-family="system-ui" font-size="10">Easy to add new subscribers without changing publisher</text>
</g>
<g transform="translate(320, 40)">
<circle cx="6" cy="6" r="4" fill="#4ade80"/>
<text x="16" y="10" fill="#a0a0a0" font-family="system-ui" font-size="10">Abstraction allows switching backends (Kafka)</text>
</g>
<g transform="translate(320, 58)">
<circle cx="6" cy="6" r="4" fill="#4ade80"/>
<text x="16" y="10" fill="#a0a0a0" font-family="system-ui" font-size="10">Natural audit trail of all events</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@@ -0,0 +1,80 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 700 350">
<defs>
<marker id="arrRight" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#e94560"/>
</marker>
</defs>
<!-- Background -->
<rect width="700" height="350" fill="#1a1a2e"/>
<!-- Title -->
<text x="350" y="30" text-anchor="middle" fill="#e94560" font-family="system-ui" font-size="16" font-weight="bold">Domain Mapping: Monitoring to Payments</text>
<!-- Left column header -->
<g transform="translate(50, 55)">
<rect width="250" height="35" rx="6" fill="#16213e" stroke="#e94560" stroke-width="2"/>
<text x="125" y="23" text-anchor="middle" fill="#e94560" font-family="system-ui" font-size="13" font-weight="bold">System Monitoring</text>
</g>
<!-- Right column header -->
<g transform="translate(400, 55)">
<rect width="250" height="35" rx="6" fill="#16213e" stroke="#4ade80" stroke-width="2"/>
<text x="125" y="23" text-anchor="middle" fill="#4ade80" font-family="system-ui" font-size="13" font-weight="bold">Payment Processing</text>
</g>
<!-- Row 1 -->
<g transform="translate(50, 105)">
<rect width="250" height="40" rx="4" fill="#0f3460"/>
<text x="125" y="17" text-anchor="middle" fill="#eee" font-family="system-ui" font-size="11" font-weight="bold">Machine</text>
<text x="125" y="32" text-anchor="middle" fill="#a0a0a0" font-family="system-ui" font-size="9">workstation, laptop, server</text>
</g>
<line x1="305" y1="125" x2="395" y2="125" stroke="#e94560" stroke-width="2" marker-end="url(#arrRight)"/>
<g transform="translate(400, 105)">
<rect width="250" height="40" rx="4" fill="#0f3460"/>
<text x="125" y="17" text-anchor="middle" fill="#eee" font-family="system-ui" font-size="11" font-weight="bold">Payment Processor</text>
<text x="125" y="32" text-anchor="middle" fill="#a0a0a0" font-family="system-ui" font-size="9">Stripe, PayPal, bank API</text>
</g>
<!-- Row 2 -->
<g transform="translate(50, 155)">
<rect width="250" height="40" rx="4" fill="#0f3460"/>
<text x="125" y="17" text-anchor="middle" fill="#eee" font-family="system-ui" font-size="11" font-weight="bold">Metrics Stream</text>
<text x="125" y="32" text-anchor="middle" fill="#a0a0a0" font-family="system-ui" font-size="9">CPU, memory, disk usage</text>
</g>
<line x1="305" y1="175" x2="395" y2="175" stroke="#e94560" stroke-width="2" marker-end="url(#arrRight)"/>
<g transform="translate(400, 155)">
<rect width="250" height="40" rx="4" fill="#0f3460"/>
<text x="125" y="17" text-anchor="middle" fill="#eee" font-family="system-ui" font-size="11" font-weight="bold">Transaction Stream</text>
<text x="125" y="32" text-anchor="middle" fill="#a0a0a0" font-family="system-ui" font-size="9">payments, refunds, disputes</text>
</g>
<!-- Row 3 -->
<g transform="translate(50, 205)">
<rect width="250" height="40" rx="4" fill="#0f3460"/>
<text x="125" y="17" text-anchor="middle" fill="#eee" font-family="system-ui" font-size="11" font-weight="bold">Alert Thresholds</text>
<text x="125" y="32" text-anchor="middle" fill="#a0a0a0" font-family="system-ui" font-size="9">CPU > 80%, disk > 90%</text>
</g>
<line x1="305" y1="225" x2="395" y2="225" stroke="#e94560" stroke-width="2" marker-end="url(#arrRight)"/>
<g transform="translate(400, 205)">
<rect width="250" height="40" rx="4" fill="#0f3460"/>
<text x="125" y="17" text-anchor="middle" fill="#eee" font-family="system-ui" font-size="11" font-weight="bold">Fraud Detection</text>
<text x="125" y="32" text-anchor="middle" fill="#a0a0a0" font-family="system-ui" font-size="9">amount > $10k, velocity checks</text>
</g>
<!-- Row 4 -->
<g transform="translate(50, 255)">
<rect width="250" height="40" rx="4" fill="#0f3460"/>
<text x="125" y="17" text-anchor="middle" fill="#eee" font-family="system-ui" font-size="11" font-weight="bold">Aggregator</text>
<text x="125" y="32" text-anchor="middle" fill="#a0a0a0" font-family="system-ui" font-size="9">normalize, store, publish</text>
</g>
<line x1="305" y1="275" x2="395" y2="275" stroke="#e94560" stroke-width="2" marker-end="url(#arrRight)"/>
<g transform="translate(400, 255)">
<rect width="250" height="40" rx="4" fill="#0f3460"/>
<text x="125" y="17" text-anchor="middle" fill="#eee" font-family="system-ui" font-size="11" font-weight="bold">Payment Hub</text>
<text x="125" y="32" text-anchor="middle" fill="#a0a0a0" font-family="system-ui" font-size="9">normalize, ledger, audit</text>
</g>
<!-- Footer note -->
<text x="350" y="330" text-anchor="middle" fill="#a0a0a0" font-family="system-ui" font-size="11" font-style="italic">Same architecture, different domain vocabulary</text>
</svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@@ -0,0 +1,125 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 700 380">
<defs>
<marker id="arrBlue" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
<polygon points="0 0, 8 3, 0 6" fill="#3b82f6"/>
</marker>
<marker id="arrPurple" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
<polygon points="0 0, 8 3, 0 6" fill="#a855f7"/>
</marker>
</defs>
<!-- Background -->
<rect width="700" height="380" fill="#1a1a2e"/>
<!-- Title -->
<text x="350" y="28" text-anchor="middle" fill="#e94560" font-family="system-ui" font-size="16" font-weight="bold">Deskmeter: Current vs Enhanced Architecture</text>
<!-- Current section -->
<g transform="translate(30, 50)">
<text x="150" y="15" text-anchor="middle" fill="#64748b" font-family="system-ui" font-size="12" font-weight="bold">CURRENT</text>
<!-- Daemon -->
<g transform="translate(20, 30)">
<rect width="100" height="50" rx="6" fill="#16213e" stroke="#64748b" stroke-width="1"/>
<text x="50" y="22" text-anchor="middle" fill="#eee" font-family="system-ui" font-size="10" font-weight="bold">dmcore</text>
<text x="50" y="38" text-anchor="middle" fill="#a0a0a0" font-family="system-ui" font-size="8">polls wmctrl</text>
</g>
<!-- MongoDB -->
<g transform="translate(160, 30)">
<rect width="80" height="50" rx="6" fill="#0f3460" stroke="#64748b" stroke-width="1"/>
<text x="40" y="22" text-anchor="middle" fill="#eee" font-family="system-ui" font-size="10" font-weight="bold">MongoDB</text>
<text x="40" y="38" text-anchor="middle" fill="#a0a0a0" font-family="system-ui" font-size="8">all data</text>
</g>
<!-- Flask -->
<g transform="translate(90, 100)">
<rect width="100" height="50" rx="6" fill="#16213e" stroke="#64748b" stroke-width="1"/>
<text x="50" y="22" text-anchor="middle" fill="#eee" font-family="system-ui" font-size="10" font-weight="bold">Flask</text>
<text x="50" y="38" text-anchor="middle" fill="#a0a0a0" font-family="system-ui" font-size="8">AJAX polling</text>
</g>
<!-- Arrows -->
<line x1="120" y1="55" x2="155" y2="55" stroke="#64748b" stroke-width="1"/>
<line x1="200" y1="80" x2="155" y2="100" stroke="#64748b" stroke-width="1"/>
</g>
<!-- Arrow between sections -->
<g transform="translate(320, 100)">
<line x1="0" y1="30" x2="50" y2="30" stroke="#e94560" stroke-width="3" marker-end="url(#arrBlue)"/>
<text x="25" y="50" text-anchor="middle" fill="#e94560" font-family="system-ui" font-size="10">enhance</text>
</g>
<!-- Enhanced section -->
<g transform="translate(380, 50)">
<text x="150" y="15" text-anchor="middle" fill="#4ade80" font-family="system-ui" font-size="12" font-weight="bold">ENHANCED</text>
<!-- Multiple machines -->
<g transform="translate(0, 30)">
<rect width="70" height="35" rx="4" fill="#16213e" stroke="#3b82f6" stroke-width="1"/>
<text x="35" y="22" text-anchor="middle" fill="#eee" font-family="system-ui" font-size="9">Machine 1</text>
</g>
<g transform="translate(0, 70)">
<rect width="70" height="35" rx="4" fill="#16213e" stroke="#3b82f6" stroke-width="1"/>
<text x="35" y="22" text-anchor="middle" fill="#eee" font-family="system-ui" font-size="9">Machine 2</text>
</g>
<!-- Event stream -->
<g transform="translate(100, 40)">
<rect width="80" height="55" rx="6" fill="#0f3460" stroke="#fbbf24" stroke-width="2"/>
<text x="40" y="22" text-anchor="middle" fill="#fbbf24" font-family="system-ui" font-size="9" font-weight="bold">Events</text>
<text x="40" y="38" text-anchor="middle" fill="#a0a0a0" font-family="system-ui" font-size="8">Pub/Sub</text>
</g>
<!-- Gateway with WebSocket -->
<g transform="translate(210, 30)">
<rect width="90" height="50" rx="6" fill="#16213e" stroke="#4ade80" stroke-width="2"/>
<text x="45" y="20" text-anchor="middle" fill="#4ade80" font-family="system-ui" font-size="9" font-weight="bold">Gateway</text>
<text x="45" y="35" text-anchor="middle" fill="#a0a0a0" font-family="system-ui" font-size="8">WebSocket</text>
<text x="45" y="45" text-anchor="middle" fill="#a0a0a0" font-family="system-ui" font-size="8">real-time</text>
</g>
<!-- Focus Alerts -->
<g transform="translate(210, 90)">
<rect width="90" height="40" rx="6" fill="#16213e" stroke="#a855f7" stroke-width="2"/>
<text x="45" y="17" text-anchor="middle" fill="#a855f7" font-family="system-ui" font-size="9" font-weight="bold">Focus Alerts</text>
<text x="45" y="32" text-anchor="middle" fill="#a0a0a0" font-family="system-ui" font-size="8">thresholds</text>
</g>
<!-- Arrows -->
<line x1="70" y1="47" x2="95" y2="60" stroke="#3b82f6" stroke-width="1.5" marker-end="url(#arrBlue)"/>
<line x1="70" y1="87" x2="95" y2="75" stroke="#3b82f6" stroke-width="1.5" marker-end="url(#arrBlue)"/>
<line x1="180" y1="60" x2="205" y2="55" stroke="#fbbf24" stroke-width="1.5"/>
<line x1="180" y1="75" x2="205" y2="110" stroke="#a855f7" stroke-width="1.5" marker-end="url(#arrPurple)"/>
</g>
<!-- Benefits list -->
<g transform="translate(30, 200)">
<rect width="640" height="160" rx="8" fill="#0f3460"/>
<text x="320" y="25" text-anchor="middle" fill="#eee" font-family="system-ui" font-size="13" font-weight="bold">What sysmonstm Patterns Add to Deskmeter</text>
<g transform="translate(30, 45)">
<circle cx="8" cy="8" r="5" fill="#4ade80"/>
<text x="22" y="12" fill="#eee" font-family="system-ui" font-size="11" font-weight="bold">Real-time updates</text>
<text x="22" y="28" fill="#a0a0a0" font-family="system-ui" font-size="10">WebSocket push instead of AJAX polling every 5 seconds</text>
</g>
<g transform="translate(30, 75)">
<circle cx="8" cy="8" r="5" fill="#3b82f6"/>
<text x="22" y="12" fill="#eee" font-family="system-ui" font-size="11" font-weight="bold">Multi-machine tracking</text>
<text x="22" y="28" fill="#a0a0a0" font-family="system-ui" font-size="10">Monitor workstation + laptop productivity in one dashboard</text>
</g>
<g transform="translate(30, 105)">
<circle cx="8" cy="8" r="5" fill="#a855f7"/>
<text x="22" y="12" fill="#eee" font-family="system-ui" font-size="11" font-weight="bold">Focus alerts</text>
<text x="22" y="28" fill="#a0a0a0" font-family="system-ui" font-size="10">Notify when context-switching too often or idle too long</text>
</g>
<g transform="translate(30, 135)">
<circle cx="8" cy="8" r="5" fill="#fbbf24"/>
<text x="22" y="12" fill="#eee" font-family="system-ui" font-size="11" font-weight="bold">Event-driven architecture</text>
<text x="22" y="28" fill="#a0a0a0" font-family="system-ui" font-size="10">Decoupled services, easy to add new subscribers</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@@ -2,6 +2,8 @@
The architecture behind sysmonstm isn't specific to system monitoring. The patterns - streaming data collection, event-driven processing, tiered storage, real-time dashboards - apply to many domains. This article explores two: payment processing systems and desktop productivity tracking. The architecture behind sysmonstm isn't specific to system monitoring. The patterns - streaming data collection, event-driven processing, tiered storage, real-time dashboards - apply to many domains. This article explores two: payment processing systems and desktop productivity tracking.
![Domain Mapping Overview](images/05-domain-mapping.svg)
## Payment Processing Systems ## Payment Processing Systems
The sysmonstm architecture was intentionally designed to map to payment processing. Here's how each component translates. The sysmonstm architecture was intentionally designed to map to payment processing. Here's how each component translates.
@@ -117,6 +119,8 @@ Current architecture:
This works, but sysmonstm patterns could enhance it significantly. This works, but sysmonstm patterns could enhance it significantly.
![Deskmeter Enhancement](images/06-deskmeter-enhancement.svg)
### Current Deskmeter Implementation ### Current Deskmeter Implementation
The core daemon (`dmapp/dmcore/main.py`) polls in a loop: The core daemon (`dmapp/dmcore/main.py`) polls in a loop:

View File

@@ -2,6 +2,8 @@
This is the story of building a distributed system monitoring platform. Not a tutorial with sanitized examples, but an explanation of the actual decisions made, the trade-offs considered, and the code that resulted. This is the story of building a distributed system monitoring platform. Not a tutorial with sanitized examples, but an explanation of the actual decisions made, the trade-offs considered, and the code that resulted.
![System Architecture Overview](images/01-architecture-overview.svg)
## The Problem ## The Problem
I have multiple development machines. A workstation, a laptop, sometimes a remote VM. Each one occasionally runs out of disk space, hits memory limits, or has a runaway process eating CPU. The pattern was always the same: something breaks, I SSH in, run `htop`, realize the problem, fix it. I have multiple development machines. A workstation, a laptop, sometimes a remote VM. Each one occasionally runs out of disk space, hits memory limits, or has a runaway process eating CPU. The pattern was always the same: something breaks, I SSH in, run `htop`, realize the problem, fix it.
@@ -29,6 +31,8 @@ service MetricsService {
The collector is the client. It streams metrics. The aggregator is the server. It receives them. When the stream ends (collector shuts down, network drops), the aggregator gets a `StreamAck` response. The collector is the client. It streams metrics. The aggregator is the server. It receives them. When the stream ends (collector shuts down, network drops), the aggregator gets a `StreamAck` response.
![gRPC Streaming Pattern](images/02-grpc-streaming.svg)
### Why This Storage Tier Approach ### Why This Storage Tier Approach
Metrics have different access patterns at different ages: Metrics have different access patterns at different ages:
@@ -45,6 +49,8 @@ Storing everything in one place forces a choice between fast reads (keep it all
The aggregator writes to both on every batch. Redis for live dashboard. TimescaleDB for history. The aggregator writes to both on every batch. Redis for live dashboard. TimescaleDB for history.
![Storage Tiers](images/03-storage-tiers.svg)
### Why Event-Driven for Alerts ### Why Event-Driven for Alerts
The alerts service needs to evaluate every metric against threshold rules. Two options: The alerts service needs to evaluate every metric against threshold rules. Two options:
@@ -70,6 +76,8 @@ class EventSubscriber(ABC):
Currently backed by Redis Pub/Sub (`shared/events/redis_pubsub.py`). The abstraction means switching to Kafka or RabbitMQ later requires implementing a new backend, not changing any service code. Currently backed by Redis Pub/Sub (`shared/events/redis_pubsub.py`). The abstraction means switching to Kafka or RabbitMQ later requires implementing a new backend, not changing any service code.
![Event-Driven Architecture](images/04-event-driven.svg)
## Phase 1: MVP - Getting Streaming to Work ## Phase 1: MVP - Getting Streaming to Work
The goal was simple: run a collector, see metrics appear in the aggregator's logs. The goal was simple: run a collector, see metrics appear in the aggregator's logs.

425
docs/explainer/viewer.html Normal file
View File

@@ -0,0 +1,425 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Loading... - System Monitor Docs</title>
<link rel="stylesheet" href="../architecture/styles.css">
<link rel="stylesheet" href="../static/prism/prism-tomorrow.min.css">
<link rel="stylesheet" href="../static/prism/prism-line-numbers.min.css">
<style>
/* Article layout */
.article-container {
max-width: 900px;
margin: 0 auto;
padding: 2rem;
}
.article-header {
margin-bottom: 2rem;
padding-bottom: 1rem;
border-bottom: 1px solid var(--border);
}
.article-header h1 {
font-size: 2rem;
color: var(--text-primary);
margin-bottom: 0.5rem;
}
.article-meta {
color: var(--text-secondary);
font-size: 0.875rem;
}
.back-link {
display: inline-flex;
align-items: center;
gap: 0.5rem;
color: var(--accent);
text-decoration: none;
margin-bottom: 1.5rem;
}
.back-link:hover {
text-decoration: underline;
}
/* Markdown content styling */
.markdown-content {
color: var(--text-primary);
line-height: 1.8;
}
.markdown-content h1 {
font-size: 2rem;
color: var(--accent);
margin: 2rem 0 1rem;
padding-bottom: 0.5rem;
border-bottom: 2px solid var(--accent);
}
.markdown-content h2 {
font-size: 1.5rem;
color: var(--text-primary);
margin: 2rem 0 1rem;
padding-bottom: 0.25rem;
border-bottom: 1px solid var(--border);
}
.markdown-content h3 {
font-size: 1.25rem;
color: var(--accent);
margin: 1.5rem 0 0.75rem;
}
.markdown-content h4 {
font-size: 1.1rem;
color: var(--text-primary);
margin: 1.25rem 0 0.5rem;
}
.markdown-content p {
margin: 1rem 0;
}
.markdown-content a {
color: var(--accent);
text-decoration: none;
}
.markdown-content a:hover {
text-decoration: underline;
}
.markdown-content ul, .markdown-content ol {
margin: 1rem 0;
padding-left: 2rem;
}
.markdown-content li {
margin: 0.5rem 0;
}
.markdown-content blockquote {
border-left: 4px solid var(--accent);
margin: 1.5rem 0;
padding: 0.5rem 1rem;
background: var(--bg-secondary);
border-radius: 0 8px 8px 0;
}
.markdown-content blockquote p {
margin: 0.5rem 0;
}
/* Code blocks */
.markdown-content pre {
margin: 1.5rem 0;
border-radius: 8px;
overflow: hidden;
}
.markdown-content pre[class*="language-"] {
background: #1e293b;
border: 1px solid var(--border);
}
.markdown-content code:not([class*="language-"]) {
background: var(--bg-secondary);
padding: 0.2rem 0.4rem;
border-radius: 4px;
font-size: 0.9em;
color: var(--accent);
font-family: 'JetBrains Mono', 'Fira Code', Consolas, monospace;
}
.markdown-content code[class*="language-"] {
font-family: 'JetBrains Mono', 'Fira Code', Consolas, monospace;
font-size: 0.875rem;
line-height: 1.6;
}
/* Tables */
.markdown-content table {
width: 100%;
border-collapse: collapse;
margin: 1.5rem 0;
font-size: 0.9rem;
}
.markdown-content th,
.markdown-content td {
padding: 0.75rem 1rem;
text-align: left;
border: 1px solid var(--border);
}
.markdown-content th {
background: var(--bg-secondary);
color: var(--text-primary);
font-weight: 600;
}
.markdown-content td {
background: var(--bg-primary);
}
.markdown-content tr:hover td {
background: var(--bg-secondary);
}
/* Strong/bold */
.markdown-content strong {
color: var(--text-primary);
font-weight: 600;
}
/* Horizontal rules */
.markdown-content hr {
border: none;
border-top: 1px solid var(--border);
margin: 2rem 0;
}
/* Images/Diagrams */
.markdown-content img {
max-width: 100%;
height: auto;
border-radius: 8px;
margin: 1.5rem 0;
}
/* Diagram container */
.diagram {
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: 8px;
padding: 1.5rem;
margin: 2rem 0;
text-align: center;
}
.diagram img {
margin: 0;
max-width: 100%;
}
.diagram-caption {
color: var(--text-secondary);
font-size: 0.875rem;
margin-top: 1rem;
font-style: italic;
}
/* Loading state */
.loading {
text-align: center;
padding: 4rem;
color: var(--text-secondary);
}
.loading::after {
content: '';
display: inline-block;
width: 20px;
height: 20px;
border: 2px solid var(--border);
border-top-color: var(--accent);
border-radius: 50%;
animation: spin 1s linear infinite;
margin-left: 1rem;
vertical-align: middle;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* Error state */
.error {
text-align: center;
padding: 4rem;
color: #ef4444;
}
/* Table of contents */
.toc {
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: 8px;
padding: 1.5rem;
margin-bottom: 2rem;
}
.toc h4 {
margin: 0 0 1rem;
color: var(--accent);
font-size: 0.875rem;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.toc ul {
list-style: none;
padding: 0;
margin: 0;
}
.toc li {
margin: 0.5rem 0;
}
.toc a {
color: var(--text-secondary);
text-decoration: none;
font-size: 0.9rem;
}
.toc a:hover {
color: var(--accent);
}
.toc .toc-h3 {
padding-left: 1rem;
font-size: 0.85rem;
}
</style>
</head>
<body>
<header>
<h1>System Monitoring Platform</h1>
<p class="subtitle">Documentation</p>
</header>
<main class="article-container">
<a href="../index.html" class="back-link">← Back to Index</a>
<div id="toc" class="toc" style="display: none;">
<h4>Contents</h4>
<ul id="toc-list"></ul>
</div>
<article id="content" class="markdown-content">
<div class="loading">Loading article</div>
</article>
</main>
<footer>
<p>System Monitoring Platform - Documentation</p>
</footer>
<!-- Markdown parser -->
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<!-- Prism syntax highlighting -->
<script src="../static/prism/prism.min.js"></script>
<script src="../static/prism/prism-python.min.js"></script>
<script src="../static/prism/prism-bash.min.js"></script>
<script src="../static/prism/prism-protobuf.min.js"></script>
<script src="../static/prism/prism-json.min.js"></script>
<script src="../static/prism/prism-line-numbers.min.js"></script>
<script>
// Get the markdown file from URL parameter
const params = new URLSearchParams(window.location.search);
const file = params.get('file');
if (!file) {
document.getElementById('content').innerHTML = '<div class="error">No file specified. Use ?file=filename.md</div>';
} else {
loadMarkdown(file);
}
async function loadMarkdown(filename) {
try {
const response = await fetch(filename);
if (!response.ok) {
throw new Error(`Failed to load ${filename}`);
}
const markdown = await response.text();
renderMarkdown(markdown, filename);
} catch (error) {
document.getElementById('content').innerHTML = `<div class="error">Error: ${error.message}</div>`;
}
}
function renderMarkdown(markdown, filename) {
// Configure marked
marked.setOptions({
gfm: true,
breaks: false,
headerIds: true,
mangle: false,
});
// Custom renderer for code blocks
const renderer = new marked.Renderer();
renderer.code = function(code, language) {
const lang = language || 'plaintext';
const langClass = `language-${lang}`;
// Escape HTML in code
const escaped = code
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
return `<pre class="line-numbers"><code class="${langClass}">${escaped}</code></pre>`;
};
// Custom heading renderer to add IDs
renderer.heading = function(text, level) {
const id = text.toLowerCase()
.replace(/<[^>]*>/g, '')
.replace(/[^\w\s-]/g, '')
.replace(/\s+/g, '-');
return `<h${level} id="${id}">${text}</h${level}>`;
};
marked.setOptions({ renderer });
// Render markdown
const html = marked.parse(markdown);
document.getElementById('content').innerHTML = html;
// Extract title from first h1
const titleMatch = markdown.match(/^#\s+(.+)$/m);
if (titleMatch) {
document.title = titleMatch[1] + ' - System Monitor Docs';
}
// Build table of contents
buildToc();
// Apply Prism highlighting
Prism.highlightAll();
}
function buildToc() {
const content = document.getElementById('content');
const headings = content.querySelectorAll('h2, h3');
const tocList = document.getElementById('toc-list');
if (headings.length < 3) {
return; // Don't show TOC for short articles
}
headings.forEach(heading => {
const li = document.createElement('li');
if (heading.tagName === 'H3') {
li.className = 'toc-h3';
}
const a = document.createElement('a');
a.href = '#' + heading.id;
a.textContent = heading.textContent;
li.appendChild(a);
tocList.appendChild(li);
});
document.getElementById('toc').style.display = 'block';
}
</script>
</body>
</html>

View File

@@ -1,10 +1,10 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>System Monitor - Documentation</title> <title>System Monitor - Documentation</title>
<link rel="stylesheet" href="architecture/styles.css"> <link rel="stylesheet" href="architecture/styles.css" />
<style> <style>
/* Additional styles for docs index */ /* Additional styles for docs index */
.nav-section { .nav-section {
@@ -34,7 +34,9 @@
border-radius: 8px; border-radius: 8px;
text-decoration: none; text-decoration: none;
border: 1px solid var(--border); border: 1px solid var(--border);
transition: border-color 0.2s, transform 0.2s; transition:
border-color 0.2s,
transform 0.2s;
} }
.doc-link:hover { .doc-link:hover {
@@ -82,38 +84,77 @@
<section class="nav-section"> <section class="nav-section">
<h2>Explainer Articles</h2> <h2>Explainer Articles</h2>
<div class="doc-links"> <div class="doc-links">
<a href="explainer/sysmonstm-from-start-to-finish.md" class="doc-link"> <a
href="explainer/viewer.html?file=sysmonstm-from-start-to-finish.md"
class="doc-link"
>
<h3>sysmonstm: From Start to Finish</h3> <h3>sysmonstm: From Start to Finish</h3>
<p>The complete story of building this monitoring platform. Architecture decisions, trade-offs, and code walkthrough from MVP to production patterns.</p> <p>
The complete story of building this monitoring
platform. Architecture decisions, trade-offs, and
code walkthrough from MVP to production patterns.
</p>
<span class="tag">Article</span> <span class="tag">Article</span>
</a> </a>
<a href="explainer/other-applications.md" class="doc-link"> <a
href="explainer/viewer.html?file=other-applications.md"
class="doc-link"
>
<h3>Same Patterns, Different Domains</h3> <h3>Same Patterns, Different Domains</h3>
<p>How the same architecture applies to payment processing systems and the Deskmeter workspace timer. Domain mapping and implementation paths.</p> <p>
How the same architecture applies to payment
processing systems and the Deskmeter workspace
timer. Domain mapping and implementation paths.
</p>
<span class="tag">Article</span> <span class="tag">Article</span>
</a> </a>
</div> </div>
</section> </section>
<hr class="section-divider"> <hr class="section-divider" />
<!-- Architecture Diagrams --> <!-- Architecture Diagrams -->
<section class="graph-section" id="overview"> <section class="graph-section" id="overview">
<div class="graph-header-row"> <div class="graph-header-row">
<h2>System Overview</h2> <h2>System Overview</h2>
<a href="architecture/graph.html?g=01-system-overview" class="view-btn">View Full</a> <a
href="architecture/graph.html?g=01-system-overview"
class="view-btn"
>View Full</a
>
</div> </div>
<a href="architecture/graph.html?g=01-system-overview" class="graph-preview"> <a
<img src="architecture/01-system-overview.svg" alt="System Overview"> href="architecture/graph.html?g=01-system-overview"
class="graph-preview"
>
<img
src="architecture/01-system-overview.svg"
alt="System Overview"
/>
</a> </a>
<div class="graph-details"> <div class="graph-details">
<p>High-level architecture showing all services, data stores, and communication patterns.</p> <p>
High-level architecture showing all services, data
stores, and communication patterns.
</p>
<h4>Key Components</h4> <h4>Key Components</h4>
<ul> <ul>
<li><strong>Collector</strong>: Runs on each monitored machine, streams metrics via gRPC</li> <li>
<li><strong>Aggregator</strong>: Central gRPC server, receives streams, normalizes data</li> <strong>Collector</strong>: Runs on each monitored
<li><strong>Gateway</strong>: FastAPI service, WebSocket for browser, REST for queries</li> machine, streams metrics via gRPC
<li><strong>Alerts</strong>: Subscribes to events, evaluates thresholds, triggers actions</li> </li>
<li>
<strong>Aggregator</strong>: Central gRPC server,
receives streams, normalizes data
</li>
<li>
<strong>Gateway</strong>: FastAPI service, WebSocket
for browser, REST for queries
</li>
<li>
<strong>Alerts</strong>: Subscribes to events,
evaluates thresholds, triggers actions
</li>
</ul> </ul>
</div> </div>
</section> </section>
@@ -121,17 +162,32 @@
<section class="graph-section" id="data-flow"> <section class="graph-section" id="data-flow">
<div class="graph-header-row"> <div class="graph-header-row">
<h2>Data Flow Pipeline</h2> <h2>Data Flow Pipeline</h2>
<a href="architecture/graph.html?g=02-data-flow" class="view-btn">View Full</a> <a
href="architecture/graph.html?g=02-data-flow"
class="view-btn"
>View Full</a
>
</div> </div>
<a href="architecture/graph.html?g=02-data-flow" class="graph-preview"> <a
<img src="architecture/02-data-flow.svg" alt="Data Flow"> href="architecture/graph.html?g=02-data-flow"
class="graph-preview"
>
<img src="architecture/02-data-flow.svg" alt="Data Flow" />
</a> </a>
<div class="graph-details"> <div class="graph-details">
<p>How metrics flow from collection through storage with different retention tiers.</p> <p>
How metrics flow from collection through storage with
different retention tiers.
</p>
<h4>Storage Tiers</h4> <h4>Storage Tiers</h4>
<table class="details-table"> <table class="details-table">
<thead> <thead>
<tr><th>Tier</th><th>Resolution</th><th>Retention</th><th>Use Case</th></tr> <tr>
<th>Tier</th>
<th>Resolution</th>
<th>Retention</th>
<th>Use Case</th>
</tr>
</thead> </thead>
<tbody> <tbody>
<tr> <tr>
@@ -166,18 +222,40 @@
<section class="graph-section" id="deployment"> <section class="graph-section" id="deployment">
<div class="graph-header-row"> <div class="graph-header-row">
<h2>Deployment Architecture</h2> <h2>Deployment Architecture</h2>
<a href="architecture/graph.html?g=03-deployment" class="view-btn">View Full</a> <a
href="architecture/graph.html?g=03-deployment"
class="view-btn"
>View Full</a
>
</div> </div>
<a href="architecture/graph.html?g=03-deployment" class="graph-preview"> <a
<img src="architecture/03-deployment.svg" alt="Deployment"> href="architecture/graph.html?g=03-deployment"
class="graph-preview"
>
<img
src="architecture/03-deployment.svg"
alt="Deployment"
/>
</a> </a>
<div class="graph-details"> <div class="graph-details">
<p>Deployment options from local development to AWS production.</p> <p>
Deployment options from local development to AWS
production.
</p>
<h4>Environments</h4> <h4>Environments</h4>
<ul> <ul>
<li><strong>Local Dev</strong>: Kind + Tilt for K8s, or Docker Compose</li> <li>
<li><strong>Demo (EC2)</strong>: Docker Compose on t2.small at sysmonstm.mcrn.ar</li> <strong>Local Dev</strong>: Kind + Tilt for K8s, or
<li><strong>Lambda Pipeline</strong>: SQS-triggered aggregation for data processing experience</li> Docker Compose
</li>
<li>
<strong>Demo (EC2)</strong>: Docker Compose on
t2.small at sysmonstm.mcrn.ar
</li>
<li>
<strong>Lambda Pipeline</strong>: SQS-triggered
aggregation for data processing experience
</li>
</ul> </ul>
</div> </div>
</section> </section>
@@ -185,23 +263,42 @@
<section class="graph-section" id="grpc"> <section class="graph-section" id="grpc">
<div class="graph-header-row"> <div class="graph-header-row">
<h2>gRPC Service Definitions</h2> <h2>gRPC Service Definitions</h2>
<a href="architecture/graph.html?g=04-grpc-services" class="view-btn">View Full</a> <a
href="architecture/graph.html?g=04-grpc-services"
class="view-btn"
>View Full</a
>
</div> </div>
<a href="architecture/graph.html?g=04-grpc-services" class="graph-preview"> <a
<img src="architecture/04-grpc-services.svg" alt="gRPC Services"> href="architecture/graph.html?g=04-grpc-services"
class="graph-preview"
>
<img
src="architecture/04-grpc-services.svg"
alt="gRPC Services"
/>
</a> </a>
<div class="graph-details"> <div class="graph-details">
<p>Protocol Buffer service and message definitions.</p> <p>Protocol Buffer service and message definitions.</p>
<h4>Services</h4> <h4>Services</h4>
<ul> <ul>
<li><strong>MetricsService</strong>: Client-side streaming for metrics ingestion</li> <li>
<li><strong>ControlService</strong>: Bidirectional streaming for collector control</li> <strong>MetricsService</strong>: Client-side
<li><strong>ConfigService</strong>: Server-side streaming for config updates</li> streaming for metrics ingestion
</li>
<li>
<strong>ControlService</strong>: Bidirectional
streaming for collector control
</li>
<li>
<strong>ConfigService</strong>: Server-side
streaming for config updates
</li>
</ul> </ul>
</div> </div>
</section> </section>
<hr class="section-divider"> <hr class="section-divider" />
<section class="findings-section"> <section class="findings-section">
<h2>Interview Talking Points</h2> <h2>Interview Talking Points</h2>
@@ -288,7 +385,9 @@
<footer> <footer>
<p>System Monitoring Platform - Documentation</p> <p>System Monitoring Platform - Documentation</p>
<p class="date">Generated: <time datetime="2025-12-31">December 2025</time></p> <p class="date">
Generated: <time datetime="2025-12-31">December 2025</time>
</p>
</footer> </footer>
</body> </body>
</html> </html>

1
docs/static/prism/prism-bash.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
docs/static/prism/prism-json.min.js vendored Normal file
View File

@@ -0,0 +1 @@
Prism.languages.json={property:{pattern:/(^|[^\\])"(?:\\.|[^\\"\r\n])*"(?=\s*:)/,lookbehind:!0,greedy:!0},string:{pattern:/(^|[^\\])"(?:\\.|[^\\"\r\n])*"(?!\s*:)/,lookbehind:!0,greedy:!0},comment:{pattern:/\/\/.*|\/\*[\s\S]*?(?:\*\/|$)/,greedy:!0},number:/-?\b\d+(?:\.\d+)?(?:e[+-]?\d+)?\b/i,punctuation:/[{}[\],]/,operator:/:/,boolean:/\b(?:false|true)\b/,null:{pattern:/\bnull\b/,alias:"keyword"}},Prism.languages.webmanifest=Prism.languages.json;

View File

@@ -0,0 +1 @@
pre[class*=language-].line-numbers{position:relative;padding-left:3.8em;counter-reset:linenumber}pre[class*=language-].line-numbers>code{position:relative;white-space:inherit}.line-numbers .line-numbers-rows{position:absolute;pointer-events:none;top:0;font-size:100%;left:-3.8em;width:3em;letter-spacing:-1px;border-right:1px solid #999;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.line-numbers-rows>span{display:block;counter-increment:linenumber}.line-numbers-rows>span:before{content:counter(linenumber);color:#999;display:block;padding-right:.8em;text-align:right}

View File

@@ -0,0 +1 @@
!function(){if("undefined"!=typeof Prism&&"undefined"!=typeof document){var e="line-numbers",n=/\n(?!$)/g,t=Prism.plugins.lineNumbers={getLine:function(n,t){if("PRE"===n.tagName&&n.classList.contains(e)){var i=n.querySelector(".line-numbers-rows");if(i){var r=parseInt(n.getAttribute("data-start"),10)||1,s=r+(i.children.length-1);t<r&&(t=r),t>s&&(t=s);var l=t-r;return i.children[l]}}},resize:function(e){r([e])},assumeViewportIndependence:!0},i=void 0;window.addEventListener("resize",(function(){t.assumeViewportIndependence&&i===window.innerWidth||(i=window.innerWidth,r(Array.prototype.slice.call(document.querySelectorAll("pre.line-numbers"))))})),Prism.hooks.add("complete",(function(t){if(t.code){var i=t.element,s=i.parentNode;if(s&&/pre/i.test(s.nodeName)&&!i.querySelector(".line-numbers-rows")&&Prism.util.isActive(i,e)){i.classList.remove(e),s.classList.add(e);var l,o=t.code.match(n),a=o?o.length+1:1,u=new Array(a+1).join("<span></span>");(l=document.createElement("span")).setAttribute("aria-hidden","true"),l.className="line-numbers-rows",l.innerHTML=u,s.hasAttribute("data-start")&&(s.style.counterReset="linenumber "+(parseInt(s.getAttribute("data-start"),10)-1)),t.element.appendChild(l),r([s]),Prism.hooks.run("line-numbers",t)}}})),Prism.hooks.add("line-numbers",(function(e){e.plugins=e.plugins||{},e.plugins.lineNumbers=!0}))}function r(e){if(0!=(e=e.filter((function(e){var n,t=(n=e,n?window.getComputedStyle?getComputedStyle(n):n.currentStyle||null:null)["white-space"];return"pre-wrap"===t||"pre-line"===t}))).length){var t=e.map((function(e){var t=e.querySelector("code"),i=e.querySelector(".line-numbers-rows");if(t&&i){var r=e.querySelector(".line-numbers-sizer"),s=t.textContent.split(n);r||((r=document.createElement("span")).className="line-numbers-sizer",t.appendChild(r)),r.innerHTML="0",r.style.display="block";var l=r.getBoundingClientRect().height;return r.innerHTML="",{element:e,lines:s,lineHeights:[],oneLinerHeight:l,sizer:r}}})).filter(Boolean);t.forEach((function(e){var n=e.sizer,t=e.lines,i=e.lineHeights,r=e.oneLinerHeight;i[t.length-1]=void 0,t.forEach((function(e,t){if(e&&e.length>1){var s=n.appendChild(document.createElement("span"));s.style.display="block",s.textContent=e}else i[t]=r}))})),t.forEach((function(e){for(var n=e.sizer,t=e.lineHeights,i=0,r=0;r<t.length;r++)void 0===t[r]&&(t[r]=n.children[i++].getBoundingClientRect().height)})),t.forEach((function(e){var n=e.sizer,t=e.element.querySelector(".line-numbers-rows");n.style.display="none",n.innerHTML="",e.lineHeights.forEach((function(e,n){t.children[n].style.height=e+"px"}))}))}}}();

View File

@@ -0,0 +1 @@
!function(e){var s=/\b(?:bool|bytes|double|s?fixed(?:32|64)|float|[su]?int(?:32|64)|string)\b/;e.languages.protobuf=e.languages.extend("clike",{"class-name":[{pattern:/(\b(?:enum|extend|message|service)\s+)[A-Za-z_]\w*(?=\s*\{)/,lookbehind:!0},{pattern:/(\b(?:rpc\s+\w+|returns)\s*\(\s*(?:stream\s+)?)\.?[A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*(?=\s*\))/,lookbehind:!0}],keyword:/\b(?:enum|extend|extensions|import|message|oneof|option|optional|package|public|repeated|required|reserved|returns|rpc(?=\s+\w)|service|stream|syntax|to)\b(?!\s*=\s*\d)/,function:/\b[a-z_]\w*(?=\s*\()/i}),e.languages.insertBefore("protobuf","operator",{map:{pattern:/\bmap<\s*[\w.]+\s*,\s*[\w.]+\s*>(?=\s+[a-z_]\w*\s*[=;])/i,alias:"class-name",inside:{punctuation:/[<>.,]/,builtin:s}},builtin:s,"positional-class-name":{pattern:/(?:\b|\B\.)[a-z_]\w*(?:\.[a-z_]\w*)*(?=\s+[a-z_]\w*\s*[=;])/i,alias:"class-name",inside:{punctuation:/\./}},annotation:{pattern:/(\[\s*)[a-z_]\w*(?=\s*=)/i,lookbehind:!0}})}(Prism);

1
docs/static/prism/prism-python.min.js vendored Normal file
View File

@@ -0,0 +1 @@
Prism.languages.python={comment:{pattern:/(^|[^\\])#.*/,lookbehind:!0,greedy:!0},"string-interpolation":{pattern:/(?:f|fr|rf)(?:("""|''')[\s\S]*?\1|("|')(?:\\.|(?!\2)[^\\\r\n])*\2)/i,greedy:!0,inside:{interpolation:{pattern:/((?:^|[^{])(?:\{\{)*)\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}])+\})+\})+\}/,lookbehind:!0,inside:{"format-spec":{pattern:/(:)[^:(){}]+(?=\}$)/,lookbehind:!0},"conversion-option":{pattern:/![sra](?=[:}]$)/,alias:"punctuation"},rest:null}},string:/[\s\S]+/}},"triple-quoted-string":{pattern:/(?:[rub]|br|rb)?("""|''')[\s\S]*?\1/i,greedy:!0,alias:"string"},string:{pattern:/(?:[rub]|br|rb)?("|')(?:\\.|(?!\1)[^\\\r\n])*\1/i,greedy:!0},function:{pattern:/((?:^|\s)def[ \t]+)[a-zA-Z_]\w*(?=\s*\()/g,lookbehind:!0},"class-name":{pattern:/(\bclass\s+)\w+/i,lookbehind:!0},decorator:{pattern:/(^[\t ]*)@\w+(?:\.\w+)*/m,lookbehind:!0,alias:["annotation","punctuation"],inside:{punctuation:/\./}},keyword:/\b(?:_(?=\s*:)|and|as|assert|async|await|break|case|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|match|nonlocal|not|or|pass|print|raise|return|try|while|with|yield)\b/,builtin:/\b(?:__import__|abs|all|any|apply|ascii|basestring|bin|bool|buffer|bytearray|bytes|callable|chr|classmethod|cmp|coerce|compile|complex|delattr|dict|dir|divmod|enumerate|eval|execfile|file|filter|float|format|frozenset|getattr|globals|hasattr|hash|help|hex|id|input|int|intern|isinstance|issubclass|iter|len|list|locals|long|map|max|memoryview|min|next|object|oct|open|ord|pow|property|range|raw_input|reduce|reload|repr|reversed|round|set|setattr|slice|sorted|staticmethod|str|sum|super|tuple|type|unichr|unicode|vars|xrange|zip)\b/,boolean:/\b(?:False|None|True)\b/,number:/\b0(?:b(?:_?[01])+|o(?:_?[0-7])+|x(?:_?[a-f0-9])+)\b|(?:\b\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\B\.\d+(?:_\d+)*)(?:e[+-]?\d+(?:_\d+)*)?j?(?!\w)/i,operator:/[-+%=]=?|!=|:=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]/,punctuation:/[{}[\];(),.:]/},Prism.languages.python["string-interpolation"].inside.interpolation.inside.rest=Prism.languages.python,Prism.languages.py=Prism.languages.python;

View File

@@ -0,0 +1 @@
code[class*=language-],pre[class*=language-]{color:#ccc;background:0 0;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#2d2d2d}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.block-comment,.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#999}.token.punctuation{color:#ccc}.token.attr-name,.token.deleted,.token.namespace,.token.tag{color:#e2777a}.token.function-name{color:#6196cc}.token.boolean,.token.function,.token.number{color:#f08d49}.token.class-name,.token.constant,.token.property,.token.symbol{color:#f8c555}.token.atrule,.token.builtin,.token.important,.token.keyword,.token.selector{color:#cc99cd}.token.attr-value,.token.char,.token.regex,.token.string,.token.variable{color:#7ec699}.token.entity,.token.operator,.token.url{color:#67cdcc}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.token.inserted{color:green}

1
docs/static/prism/prism.min.js vendored Normal file

File diff suppressed because one or more lines are too long