migrated spr stuff
78
atlas/books/sysmonstm/architecture/01-system-overview.dot
Normal file
@@ -0,0 +1,78 @@
|
||||
digraph SystemOverview {
|
||||
// Graph settings
|
||||
rankdir=TB;
|
||||
compound=true;
|
||||
fontname="Helvetica";
|
||||
node [fontname="Helvetica", fontsize=11];
|
||||
edge [fontname="Helvetica", fontsize=10];
|
||||
|
||||
// Title
|
||||
labelloc="t";
|
||||
label="System Monitoring Platform - Architecture Overview";
|
||||
fontsize=16;
|
||||
|
||||
// Styling
|
||||
node [shape=box, style="rounded,filled"];
|
||||
|
||||
// External
|
||||
subgraph cluster_external {
|
||||
label="External";
|
||||
style=dashed;
|
||||
color=gray;
|
||||
|
||||
browser [label="Browser\n(Dashboard)", fillcolor="#E3F2FD"];
|
||||
machines [label="Monitored\nMachines", fillcolor="#FFF3E0", shape=box3d];
|
||||
}
|
||||
|
||||
// Core Services
|
||||
subgraph cluster_services {
|
||||
label="Application Services";
|
||||
style=filled;
|
||||
color="#E8F5E9";
|
||||
fillcolor="#E8F5E9";
|
||||
|
||||
gateway [label="Gateway\n(FastAPI)", fillcolor="#C8E6C9"];
|
||||
aggregator [label="Aggregator\n(gRPC Server)", fillcolor="#C8E6C9"];
|
||||
alerts [label="Alerts\nService", fillcolor="#C8E6C9"];
|
||||
collector [label="Collector\n(gRPC Client)", fillcolor="#DCEDC8"];
|
||||
}
|
||||
|
||||
// Data Layer
|
||||
subgraph cluster_data {
|
||||
label="Data Layer";
|
||||
style=filled;
|
||||
color="#FFF8E1";
|
||||
fillcolor="#FFF8E1";
|
||||
|
||||
redis [label="Redis\n(Pub/Sub + State)", fillcolor="#FFECB3", shape=cylinder];
|
||||
timescale [label="TimescaleDB\n(Time-series)", fillcolor="#FFECB3", shape=cylinder];
|
||||
}
|
||||
|
||||
// Event Stream
|
||||
subgraph cluster_events {
|
||||
label="Event Stream";
|
||||
style=filled;
|
||||
color="#F3E5F5";
|
||||
fillcolor="#F3E5F5";
|
||||
|
||||
events [label="Redis Pub/Sub\n(Events)", fillcolor="#E1BEE7", shape=hexagon];
|
||||
}
|
||||
|
||||
// Connections
|
||||
browser -> gateway [label="WebSocket\nREST", color="#1976D2"];
|
||||
gateway -> aggregator [label="gRPC", color="#388E3C"];
|
||||
gateway -> redis [label="State\nQuery", style=dashed];
|
||||
gateway -> timescale [label="Historical\nQuery", style=dashed];
|
||||
|
||||
machines -> collector [label="psutil", color="#F57C00", style=dotted];
|
||||
collector -> aggregator [label="gRPC\nStream", color="#388E3C"];
|
||||
|
||||
aggregator -> redis [label="Current\nState", color="#FFA000"];
|
||||
aggregator -> timescale [label="Store\nMetrics", color="#FFA000"];
|
||||
aggregator -> events [label="Publish", color="#7B1FA2"];
|
||||
|
||||
events -> alerts [label="Subscribe", color="#7B1FA2"];
|
||||
events -> gateway [label="Subscribe", color="#7B1FA2"];
|
||||
|
||||
alerts -> timescale [label="Store\nAlerts", style=dashed];
|
||||
}
|
||||
193
atlas/books/sysmonstm/architecture/01-system-overview.svg
Normal file
@@ -0,0 +1,193 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 14.1.1 (0)
|
||||
-->
|
||||
<!-- Title: SystemOverview Pages: 1 -->
|
||||
<svg width="444pt" height="508pt"
|
||||
viewBox="0.00 0.00 444.00 508.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 503.78)">
|
||||
<title>SystemOverview</title>
|
||||
<polygon fill="white" stroke="none" points="-4,4 -4,-503.78 440,-503.78 440,4 -4,4"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="218" y="-480.58" font-family="Helvetica,sans-Serif" font-size="16.00">System Monitoring Platform - Architecture Overview</text>
|
||||
<g id="clust1" class="cluster">
|
||||
<title>cluster_external</title>
|
||||
<polygon fill="none" stroke="gray" stroke-dasharray="5,2" points="45.5,-374.2 45.5,-453.7 235.5,-453.7 235.5,-374.2 45.5,-374.2"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="140.5" y="-434.5" font-family="Helvetica,sans-Serif" font-size="16.00">External</text>
|
||||
</g>
|
||||
<g id="clust2" class="cluster">
|
||||
<title>cluster_services</title>
|
||||
<polygon fill="#e8f5e9" stroke="#e8f5e9" points="101.5,-143.12 101.5,-320.12 363.5,-320.12 363.5,-143.12 101.5,-143.12"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="232.5" y="-300.93" font-family="Helvetica,sans-Serif" font-size="16.00">Application Services</text>
|
||||
</g>
|
||||
<g id="clust3" class="cluster">
|
||||
<title>cluster_data</title>
|
||||
<polygon fill="#fff8e1" stroke="#fff8e1" points="22.5,-8 22.5,-99.62 260.5,-99.62 260.5,-8 22.5,-8"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="141.5" y="-80.42" font-family="Helvetica,sans-Serif" font-size="16.00">Data Layer</text>
|
||||
</g>
|
||||
<g id="clust4" class="cluster">
|
||||
<title>cluster_events</title>
|
||||
<polygon fill="#f3e5f5" stroke="#f3e5f5" points="243.5,-363.62 243.5,-464.28 413.5,-464.28 413.5,-363.62 243.5,-363.62"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="328.5" y="-445.08" font-family="Helvetica,sans-Serif" font-size="16.00">Event Stream</text>
|
||||
</g>
|
||||
<!-- browser -->
|
||||
<g id="node1" class="node">
|
||||
<title>browser</title>
|
||||
<path fill="#e3f2fd" stroke="black" d="M125.62,-418.2C125.62,-418.2 65.38,-418.2 65.38,-418.2 59.38,-418.2 53.38,-412.2 53.38,-406.2 53.38,-406.2 53.38,-394.2 53.38,-394.2 53.38,-388.2 59.38,-382.2 65.38,-382.2 65.38,-382.2 125.62,-382.2 125.62,-382.2 131.62,-382.2 137.62,-388.2 137.62,-394.2 137.62,-394.2 137.62,-406.2 137.62,-406.2 137.62,-412.2 131.62,-418.2 125.62,-418.2"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="95.5" y="-403.25" font-family="Helvetica,sans-Serif" font-size="11.00">Browser</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="95.5" y="-389.75" font-family="Helvetica,sans-Serif" font-size="11.00">(Dashboard)</text>
|
||||
</g>
|
||||
<!-- gateway -->
|
||||
<g id="node3" class="node">
|
||||
<title>gateway</title>
|
||||
<path fill="#c8e6c9" stroke="black" d="M161.88,-284.62C161.88,-284.62 121.12,-284.62 121.12,-284.62 115.12,-284.62 109.12,-278.62 109.12,-272.62 109.12,-272.62 109.12,-260.62 109.12,-260.62 109.12,-254.62 115.12,-248.62 121.12,-248.62 121.12,-248.62 161.88,-248.62 161.88,-248.62 167.88,-248.62 173.88,-254.62 173.88,-260.62 173.88,-260.62 173.88,-272.62 173.88,-272.62 173.88,-278.62 167.88,-284.62 161.88,-284.62"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="141.5" y="-269.68" font-family="Helvetica,sans-Serif" font-size="11.00">Gateway</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="141.5" y="-256.18" font-family="Helvetica,sans-Serif" font-size="11.00">(FastAPI)</text>
|
||||
</g>
|
||||
<!-- browser->gateway -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>browser->gateway</title>
|
||||
<path fill="none" stroke="#1976d2" d="M92.73,-381.75C91.08,-367.05 90.32,-345.66 96.25,-328.12 100.5,-315.57 108.45,-303.5 116.51,-293.49"/>
|
||||
<polygon fill="#1976d2" stroke="#1976d2" points="119.02,-295.94 122.86,-286.06 113.7,-291.39 119.02,-295.94"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="122.88" y="-344.12" font-family="Helvetica,sans-Serif" font-size="10.00">WebSocket</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="122.88" y="-331.38" font-family="Helvetica,sans-Serif" font-size="10.00">REST</text>
|
||||
</g>
|
||||
<!-- machines -->
|
||||
<g id="node2" class="node">
|
||||
<title>machines</title>
|
||||
<polygon fill="#fff3e0" stroke="black" points="227.25,-418.2 159.75,-418.2 155.75,-414.2 155.75,-382.2 223.25,-382.2 227.25,-386.2 227.25,-418.2"/>
|
||||
<polyline fill="none" stroke="black" points="223.25,-414.2 155.75,-414.2"/>
|
||||
<polyline fill="none" stroke="black" points="223.25,-414.2 223.25,-382.2"/>
|
||||
<polyline fill="none" stroke="black" points="223.25,-414.2 227.25,-418.2"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="191.5" y="-403.25" font-family="Helvetica,sans-Serif" font-size="11.00">Monitored</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="191.5" y="-389.75" font-family="Helvetica,sans-Serif" font-size="11.00">Machines</text>
|
||||
</g>
|
||||
<!-- collector -->
|
||||
<g id="node6" class="node">
|
||||
<title>collector</title>
|
||||
<path fill="#dcedc8" stroke="black" d="M343.88,-284.62C343.88,-284.62 279.12,-284.62 279.12,-284.62 273.12,-284.62 267.12,-278.62 267.12,-272.62 267.12,-272.62 267.12,-260.62 267.12,-260.62 267.12,-254.62 273.12,-248.62 279.12,-248.62 279.12,-248.62 343.88,-248.62 343.88,-248.62 349.88,-248.62 355.88,-254.62 355.88,-260.62 355.88,-260.62 355.88,-272.62 355.88,-272.62 355.88,-278.62 349.88,-284.62 343.88,-284.62"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="311.5" y="-269.68" font-family="Helvetica,sans-Serif" font-size="11.00">Collector</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="311.5" y="-256.18" font-family="Helvetica,sans-Serif" font-size="11.00">(gRPC Client)</text>
|
||||
</g>
|
||||
<!-- machines->collector -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>machines->collector</title>
|
||||
<path fill="none" stroke="#f57c00" stroke-dasharray="1,5" d="M210.81,-381.83C219.12,-375.21 229.26,-368.17 239.5,-363.62 260.21,-354.43 273.06,-369.22 289.5,-353.62 304.98,-338.94 310.15,-314.98 311.64,-296.08"/>
|
||||
<polygon fill="#f57c00" stroke="#f57c00" points="315.12,-296.47 312.08,-286.32 308.13,-296.15 315.12,-296.47"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="318.1" y="-337.75" font-family="Helvetica,sans-Serif" font-size="10.00">psutil</text>
|
||||
</g>
|
||||
<!-- aggregator -->
|
||||
<g id="node4" class="node">
|
||||
<title>aggregator</title>
|
||||
<path fill="#c8e6c9" stroke="black" d="M343.12,-187.12C343.12,-187.12 273.88,-187.12 273.88,-187.12 267.88,-187.12 261.88,-181.12 261.88,-175.12 261.88,-175.12 261.88,-163.12 261.88,-163.12 261.88,-157.12 267.88,-151.12 273.88,-151.12 273.88,-151.12 343.12,-151.12 343.12,-151.12 349.12,-151.12 355.12,-157.12 355.12,-163.12 355.12,-163.12 355.12,-175.12 355.12,-175.12 355.12,-181.12 349.12,-187.12 343.12,-187.12"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="308.5" y="-172.18" font-family="Helvetica,sans-Serif" font-size="11.00">Aggregator</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="308.5" y="-158.68" font-family="Helvetica,sans-Serif" font-size="11.00">(gRPC Server)</text>
|
||||
</g>
|
||||
<!-- gateway->aggregator -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>gateway->aggregator</title>
|
||||
<path fill="none" stroke="#388e3c" d="M171.74,-248.33C198.77,-232.88 238.56,-210.12 268.26,-193.13"/>
|
||||
<polygon fill="#388e3c" stroke="#388e3c" points="269.66,-196.37 276.6,-188.36 266.19,-190.29 269.66,-196.37"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="257.62" y="-214.75" font-family="Helvetica,sans-Serif" font-size="10.00">gRPC</text>
|
||||
</g>
|
||||
<!-- redis -->
|
||||
<g id="node7" class="node">
|
||||
<title>redis</title>
|
||||
<path fill="#ffecb3" stroke="black" d="M146,-59.75C146,-62.16 120.23,-64.12 88.5,-64.12 56.77,-64.12 31,-62.16 31,-59.75 31,-59.75 31,-20.38 31,-20.38 31,-17.96 56.77,-16 88.5,-16 120.23,-16 146,-17.96 146,-20.38 146,-20.38 146,-59.75 146,-59.75"/>
|
||||
<path fill="none" stroke="black" d="M146,-59.75C146,-57.34 120.23,-55.38 88.5,-55.38 56.77,-55.38 31,-57.34 31,-59.75"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="88.5" y="-43.11" font-family="Helvetica,sans-Serif" font-size="11.00">Redis</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="88.5" y="-29.61" font-family="Helvetica,sans-Serif" font-size="11.00">(Pub/Sub + State)</text>
|
||||
</g>
|
||||
<!-- gateway->redis -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>gateway->redis</title>
|
||||
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M122.74,-248.35C108.28,-233.68 89.42,-211.2 81.25,-187.12 68.86,-150.62 73.72,-106.03 79.72,-75.79"/>
|
||||
<polygon fill="black" stroke="black" points="83.14,-76.56 81.82,-66.04 76.29,-75.08 83.14,-76.56"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="95.88" y="-172.38" font-family="Helvetica,sans-Serif" font-size="10.00">State</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="95.88" y="-159.62" font-family="Helvetica,sans-Serif" font-size="10.00">Query</text>
|
||||
</g>
|
||||
<!-- timescale -->
|
||||
<g id="node8" class="node">
|
||||
<title>timescale</title>
|
||||
<path fill="#ffecb3" stroke="black" d="M252.88,-59.75C252.88,-62.16 232.99,-64.12 208.5,-64.12 184.01,-64.12 164.12,-62.16 164.12,-59.75 164.12,-59.75 164.12,-20.38 164.12,-20.38 164.12,-17.96 184.01,-16 208.5,-16 232.99,-16 252.88,-17.96 252.88,-20.38 252.88,-20.38 252.88,-59.75 252.88,-59.75"/>
|
||||
<path fill="none" stroke="black" d="M252.88,-59.75C252.88,-57.34 232.99,-55.38 208.5,-55.38 184.01,-55.38 164.12,-57.34 164.12,-59.75"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="208.5" y="-43.11" font-family="Helvetica,sans-Serif" font-size="11.00">TimescaleDB</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="208.5" y="-29.61" font-family="Helvetica,sans-Serif" font-size="11.00">(Time-series)</text>
|
||||
</g>
|
||||
<!-- gateway->timescale -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>gateway->timescale</title>
|
||||
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M143.41,-248.29C146.34,-224.28 152.82,-179.73 164,-143.12 171.19,-119.57 182.25,-94.18 191.54,-74.62"/>
|
||||
<polygon fill="black" stroke="black" points="194.62,-76.29 195.83,-65.76 188.32,-73.24 194.62,-76.29"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="187.25" y="-172.38" font-family="Helvetica,sans-Serif" font-size="10.00">Historical</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="187.25" y="-159.62" font-family="Helvetica,sans-Serif" font-size="10.00">Query</text>
|
||||
</g>
|
||||
<!-- aggregator->redis -->
|
||||
<g id="edge7" class="edge">
|
||||
<title>aggregator->redis</title>
|
||||
<path fill="none" stroke="#ffa000" d="M267.27,-150.69C261,-148.11 254.59,-145.52 248.5,-143.12 236.59,-138.44 233.22,-138.25 221.5,-133.12 191.36,-119.95 182.76,-118.04 155.5,-99.62 143.6,-91.59 131.5,-81.66 120.93,-72.28"/>
|
||||
<polygon fill="#ffa000" stroke="#ffa000" points="123.32,-69.73 113.56,-65.6 118.62,-74.91 123.32,-69.73"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="239.5" y="-123.62" font-family="Helvetica,sans-Serif" font-size="10.00">Current</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="239.5" y="-110.88" font-family="Helvetica,sans-Serif" font-size="10.00">State</text>
|
||||
</g>
|
||||
<!-- aggregator->timescale -->
|
||||
<g id="edge8" class="edge">
|
||||
<title>aggregator->timescale</title>
|
||||
<path fill="none" stroke="#ffa000" d="M294.81,-150.72C279.15,-130.84 253.2,-97.86 233.84,-73.25"/>
|
||||
<polygon fill="#ffa000" stroke="#ffa000" points="236.64,-71.16 227.71,-65.47 231.14,-75.49 236.64,-71.16"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="296.95" y="-123.62" font-family="Helvetica,sans-Serif" font-size="10.00">Store</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="296.95" y="-110.88" font-family="Helvetica,sans-Serif" font-size="10.00">Metrics</text>
|
||||
</g>
|
||||
<!-- events -->
|
||||
<g id="node9" class="node">
|
||||
<title>events</title>
|
||||
<path fill="#e1bee7" stroke="black" d="M395.63,-407.37C395.63,-407.37 376.5,-421.61 376.5,-421.61 371.69,-425.2 360.88,-428.78 354.88,-428.78 354.88,-428.78 302.12,-428.78 302.12,-428.78 296.12,-428.78 285.31,-425.2 280.5,-421.61 280.5,-421.61 261.37,-407.37 261.37,-407.37 256.56,-403.79 256.56,-396.62 261.37,-393.04 261.37,-393.04 280.5,-378.79 280.5,-378.79 285.31,-375.21 296.12,-371.62 302.12,-371.62 302.12,-371.62 354.88,-371.62 354.88,-371.62 360.88,-371.62 371.69,-375.21 376.5,-378.79 376.5,-378.79 395.63,-393.04 395.63,-393.04 400.44,-396.62 400.44,-403.79 395.63,-407.37"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="328.5" y="-403.25" font-family="Helvetica,sans-Serif" font-size="11.00">Redis Pub/Sub</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="328.5" y="-389.75" font-family="Helvetica,sans-Serif" font-size="11.00">(Events)</text>
|
||||
</g>
|
||||
<!-- aggregator->events -->
|
||||
<g id="edge9" class="edge">
|
||||
<title>aggregator->events</title>
|
||||
<path fill="none" stroke="#7b1fa2" d="M333.16,-187.49C339.14,-192.63 345.07,-198.63 349.5,-205.12 361.02,-222.03 361.12,-228.46 364.5,-248.62 369.75,-279.97 371.24,-289.07 364.5,-320.12 361.48,-334.06 355.78,-348.49 349.79,-361.14"/>
|
||||
<polygon fill="#7b1fa2" stroke="#7b1fa2" points="346.73,-359.44 345.42,-369.95 353,-362.55 346.73,-359.44"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="386.64" y="-263.5" font-family="Helvetica,sans-Serif" font-size="10.00">Publish</text>
|
||||
</g>
|
||||
<!-- alerts -->
|
||||
<g id="node5" class="node">
|
||||
<title>alerts</title>
|
||||
<path fill="#c8e6c9" stroke="black" d="M236.75,-284.62C236.75,-284.62 204.25,-284.62 204.25,-284.62 198.25,-284.62 192.25,-278.62 192.25,-272.62 192.25,-272.62 192.25,-260.62 192.25,-260.62 192.25,-254.62 198.25,-248.62 204.25,-248.62 204.25,-248.62 236.75,-248.62 236.75,-248.62 242.75,-248.62 248.75,-254.62 248.75,-260.62 248.75,-260.62 248.75,-272.62 248.75,-272.62 248.75,-278.62 242.75,-284.62 236.75,-284.62"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="220.5" y="-269.68" font-family="Helvetica,sans-Serif" font-size="11.00">Alerts</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="220.5" y="-256.18" font-family="Helvetica,sans-Serif" font-size="11.00">Service</text>
|
||||
</g>
|
||||
<!-- alerts->timescale -->
|
||||
<g id="edge12" class="edge">
|
||||
<title>alerts->timescale</title>
|
||||
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M219.58,-248.38C217.61,-211.47 212.94,-124.24 210.34,-75.51"/>
|
||||
<polygon fill="black" stroke="black" points="213.85,-75.6 209.82,-65.8 206.86,-75.97 213.85,-75.6"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="230.53" y="-172.38" font-family="Helvetica,sans-Serif" font-size="10.00">Store</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="230.53" y="-159.62" font-family="Helvetica,sans-Serif" font-size="10.00">Alerts</text>
|
||||
</g>
|
||||
<!-- collector->aggregator -->
|
||||
<g id="edge6" class="edge">
|
||||
<title>collector->aggregator</title>
|
||||
<path fill="none" stroke="#388e3c" d="M310.96,-248.55C310.53,-234.65 309.9,-214.73 309.39,-198.45"/>
|
||||
<polygon fill="#388e3c" stroke="#388e3c" points="312.9,-198.77 309.09,-188.89 305.91,-198.99 312.9,-198.77"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="327.98" y="-221.12" font-family="Helvetica,sans-Serif" font-size="10.00">gRPC</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="327.98" y="-208.38" font-family="Helvetica,sans-Serif" font-size="10.00">Stream</text>
|
||||
</g>
|
||||
<!-- events->gateway -->
|
||||
<g id="edge11" class="edge">
|
||||
<title>events->gateway</title>
|
||||
<path fill="none" stroke="#7b1fa2" d="M281.13,-378.02C267.86,-372.71 253.29,-367.44 239.5,-363.62 212.49,-356.16 199.25,-370.98 177.25,-353.62 159.49,-339.61 150.46,-315.21 145.93,-295.98"/>
|
||||
<polygon fill="#7b1fa2" stroke="#7b1fa2" points="149.38,-295.39 143.95,-286.29 142.52,-296.79 149.38,-295.39"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="200.88" y="-337.75" font-family="Helvetica,sans-Serif" font-size="10.00">Subscribe</text>
|
||||
</g>
|
||||
<!-- events->alerts -->
|
||||
<g id="edge10" class="edge">
|
||||
<title>events->alerts</title>
|
||||
<path fill="none" stroke="#7b1fa2" d="M277.27,-380.98C264.23,-374.18 251.36,-365.21 242.25,-353.62 229.43,-337.32 224.08,-314.36 221.89,-296.26"/>
|
||||
<polygon fill="#7b1fa2" stroke="#7b1fa2" points="225.38,-296.07 220.98,-286.43 218.41,-296.71 225.38,-296.07"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="265.88" y="-337.75" font-family="Helvetica,sans-Serif" font-size="10.00">Subscribe</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 16 KiB |
83
atlas/books/sysmonstm/architecture/02-data-flow.dot
Normal file
@@ -0,0 +1,83 @@
|
||||
digraph DataFlow {
|
||||
rankdir=LR;
|
||||
compound=true;
|
||||
fontname="Helvetica";
|
||||
node [fontname="Helvetica", fontsize=10];
|
||||
edge [fontname="Helvetica", fontsize=9];
|
||||
|
||||
labelloc="t";
|
||||
label="Metrics Data Flow Pipeline";
|
||||
fontsize=14;
|
||||
|
||||
node [shape=box, style="rounded,filled"];
|
||||
|
||||
// Collection
|
||||
subgraph cluster_collect {
|
||||
label="Collection (5s)";
|
||||
style=filled;
|
||||
fillcolor="#E3F2FD";
|
||||
|
||||
psutil [label="psutil\n(CPU, Mem, Disk)", shape=component, fillcolor="#BBDEFB"];
|
||||
collector [label="Collector\nService", fillcolor="#90CAF9"];
|
||||
}
|
||||
|
||||
// Ingestion
|
||||
subgraph cluster_ingest {
|
||||
label="Ingestion";
|
||||
style=filled;
|
||||
fillcolor="#E8F5E9";
|
||||
|
||||
aggregator [label="Aggregator\n(gRPC)", fillcolor="#A5D6A7"];
|
||||
validate [label="Validate &\nNormalize", shape=diamond, fillcolor="#C8E6C9"];
|
||||
}
|
||||
|
||||
// Storage Hot
|
||||
subgraph cluster_hot {
|
||||
label="Hot Path (Real-time)";
|
||||
style=filled;
|
||||
fillcolor="#FFF3E0";
|
||||
|
||||
redis_state [label="Redis\nCurrent State", shape=cylinder, fillcolor="#FFCC80"];
|
||||
redis_pubsub [label="Redis\nPub/Sub", shape=hexagon, fillcolor="#FFB74D"];
|
||||
}
|
||||
|
||||
// Storage Warm
|
||||
subgraph cluster_warm {
|
||||
label="Warm Path (Historical)";
|
||||
style=filled;
|
||||
fillcolor="#FCE4EC";
|
||||
|
||||
raw [label="metrics_raw\n(5s, 24h)", shape=cylinder, fillcolor="#F8BBD9"];
|
||||
agg_1m [label="metrics_1m\n(1m, 7d)", shape=cylinder, fillcolor="#F48FB1"];
|
||||
agg_1h [label="metrics_1h\n(1h, 90d)", shape=cylinder, fillcolor="#EC407A"];
|
||||
}
|
||||
|
||||
// Consumers
|
||||
subgraph cluster_consume {
|
||||
label="Consumers";
|
||||
style=filled;
|
||||
fillcolor="#E8EAF6";
|
||||
|
||||
alerts [label="Alert\nService", fillcolor="#C5CAE9"];
|
||||
gateway [label="Gateway\n(WebSocket)", fillcolor="#9FA8DA"];
|
||||
lambda [label="Lambda\nAggregator", fillcolor="#7986CB", style="rounded,filled,dashed"];
|
||||
}
|
||||
|
||||
// Flow
|
||||
psutil -> collector [label="Metrics"];
|
||||
collector -> aggregator [label="gRPC\nStream"];
|
||||
aggregator -> validate;
|
||||
|
||||
validate -> redis_state [label="Upsert"];
|
||||
validate -> redis_pubsub [label="Publish"];
|
||||
validate -> raw [label="Insert"];
|
||||
|
||||
redis_pubsub -> alerts [label="metrics.*"];
|
||||
redis_pubsub -> gateway [label="metrics.*"];
|
||||
|
||||
raw -> agg_1m [label="Continuous\nAggregate", style=dashed];
|
||||
agg_1m -> agg_1h [label="Hourly\nJob", style=dashed];
|
||||
|
||||
raw -> lambda [label="SQS\nTrigger", style=dotted];
|
||||
lambda -> agg_1m [label="Batch\nWrite", style=dotted];
|
||||
}
|
||||
217
atlas/books/sysmonstm/architecture/02-data-flow.svg
Normal file
@@ -0,0 +1,217 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 14.1.1 (0)
|
||||
-->
|
||||
<!-- Title: DataFlow Pages: 1 -->
|
||||
<svg width="1087pt" height="329pt"
|
||||
viewBox="0.00 0.00 1087.00 329.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 325.25)">
|
||||
<title>DataFlow</title>
|
||||
<polygon fill="white" stroke="none" points="-4,4 -4,-325.25 1082.5,-325.25 1082.5,4 -4,4"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="539.25" y="-303.95" font-family="Helvetica,sans-Serif" font-size="14.00">Metrics Data Flow Pipeline</text>
|
||||
<g id="clust1" class="cluster">
|
||||
<title>cluster_collect</title>
|
||||
<polygon fill="#e3f2fd" stroke="black" points="8,-111 8,-188 254,-188 254,-111 8,-111"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="131" y="-170.7" font-family="Helvetica,sans-Serif" font-size="14.00">Collection (5s)</text>
|
||||
</g>
|
||||
<g id="clust2" class="cluster">
|
||||
<title>cluster_ingest</title>
|
||||
<polygon fill="#e8f5e9" stroke="black" points="307,-95 307,-204 562.5,-204 562.5,-95 307,-95"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="434.75" y="-186.7" font-family="Helvetica,sans-Serif" font-size="14.00">Ingestion</text>
|
||||
</g>
|
||||
<g id="clust3" class="cluster">
|
||||
<title>cluster_hot</title>
|
||||
<polygon fill="#fff3e0" stroke="black" points="614.75,-34 614.75,-193 769.5,-193 769.5,-34 614.75,-34"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="692.12" y="-175.7" font-family="Helvetica,sans-Serif" font-size="14.00">Hot Path (Real-time)</text>
|
||||
</g>
|
||||
<g id="clust4" class="cluster">
|
||||
<title>cluster_warm</title>
|
||||
<polygon fill="#fce4ec" stroke="black" points="645.62,-201 645.62,-288 1070.5,-288 1070.5,-201 645.62,-201"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="858.06" y="-270.7" font-family="Helvetica,sans-Serif" font-size="14.00">Warm Path (Historical)</text>
|
||||
</g>
|
||||
<g id="clust5" class="cluster">
|
||||
<title>cluster_consume</title>
|
||||
<polygon fill="#e8eaf6" stroke="black" points="840.5,-8 840.5,-193 935.25,-193 935.25,-8 840.5,-8"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="887.88" y="-175.7" font-family="Helvetica,sans-Serif" font-size="14.00">Consumers</text>
|
||||
</g>
|
||||
<!-- psutil -->
|
||||
<g id="node1" class="node">
|
||||
<title>psutil</title>
|
||||
<polygon fill="#bbdefb" stroke="black" points="118.25,-155 16,-155 16,-151 12,-151 12,-147 16,-147 16,-127 12,-127 12,-123 16,-123 16,-119 118.25,-119 118.25,-155"/>
|
||||
<polyline fill="none" stroke="black" points="16,-151 20,-151 20,-147 16,-147"/>
|
||||
<polyline fill="none" stroke="black" points="16,-127 20,-127 20,-123 16,-123"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="67.13" y="-140.25" font-family="Helvetica,sans-Serif" font-size="10.00">psutil</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="67.13" y="-127.5" font-family="Helvetica,sans-Serif" font-size="10.00">(CPU, Mem, Disk)</text>
|
||||
</g>
|
||||
<!-- collector -->
|
||||
<g id="node2" class="node">
|
||||
<title>collector</title>
|
||||
<path fill="#90caf9" stroke="black" d="M234,-155C234,-155 198.5,-155 198.5,-155 192.5,-155 186.5,-149 186.5,-143 186.5,-143 186.5,-131 186.5,-131 186.5,-125 192.5,-119 198.5,-119 198.5,-119 234,-119 234,-119 240,-119 246,-125 246,-131 246,-131 246,-143 246,-143 246,-149 240,-155 234,-155"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="216.25" y="-140.25" font-family="Helvetica,sans-Serif" font-size="10.00">Collector</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="216.25" y="-127.5" font-family="Helvetica,sans-Serif" font-size="10.00">Service</text>
|
||||
</g>
|
||||
<!-- psutil->collector -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>psutil->collector</title>
|
||||
<path fill="none" stroke="black" d="M118.35,-137C136.74,-137 157.31,-137 174.75,-137"/>
|
||||
<polygon fill="black" stroke="black" points="174.75,-140.5 184.75,-137 174.75,-133.5 174.75,-140.5"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="152.38" y="-139.7" font-family="Helvetica,sans-Serif" font-size="9.00">Metrics</text>
|
||||
</g>
|
||||
<!-- aggregator -->
|
||||
<g id="node3" class="node">
|
||||
<title>aggregator</title>
|
||||
<path fill="#a5d6a7" stroke="black" d="M373,-155C373,-155 327,-155 327,-155 321,-155 315,-149 315,-143 315,-143 315,-131 315,-131 315,-125 321,-119 327,-119 327,-119 373,-119 373,-119 379,-119 385,-125 385,-131 385,-131 385,-143 385,-143 385,-149 379,-155 373,-155"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="350" y="-140.25" font-family="Helvetica,sans-Serif" font-size="10.00">Aggregator</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="350" y="-127.5" font-family="Helvetica,sans-Serif" font-size="10.00">(gRPC)</text>
|
||||
</g>
|
||||
<!-- collector->aggregator -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>collector->aggregator</title>
|
||||
<path fill="none" stroke="black" d="M246.49,-137C263.19,-137 284.49,-137 303.35,-137"/>
|
||||
<polygon fill="black" stroke="black" points="303.2,-140.5 313.2,-137 303.2,-133.5 303.2,-140.5"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="280.5" y="-150.95" font-family="Helvetica,sans-Serif" font-size="9.00">gRPC</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="280.5" y="-139.7" font-family="Helvetica,sans-Serif" font-size="9.00">Stream</text>
|
||||
</g>
|
||||
<!-- validate -->
|
||||
<g id="node4" class="node">
|
||||
<title>validate</title>
|
||||
<path fill="#c8e6c9" stroke="black" d="M477.54,-165.08C477.54,-165.08 432.71,-142.42 432.71,-142.42 427.35,-139.71 427.35,-134.29 432.71,-131.58 432.71,-131.58 477.54,-108.92 477.54,-108.92 482.9,-106.21 493.6,-106.21 498.96,-108.92 498.96,-108.92 543.79,-131.58 543.79,-131.58 549.15,-134.29 549.15,-139.71 543.79,-142.42 543.79,-142.42 498.96,-165.08 498.96,-165.08 493.6,-167.79 482.9,-167.79 477.54,-165.08"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="488.25" y="-140.25" font-family="Helvetica,sans-Serif" font-size="10.00">Validate &</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="488.25" y="-127.5" font-family="Helvetica,sans-Serif" font-size="10.00">Normalize</text>
|
||||
</g>
|
||||
<!-- aggregator->validate -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>aggregator->validate</title>
|
||||
<path fill="none" stroke="black" d="M385.38,-137C392.95,-137 401.25,-137 409.76,-137"/>
|
||||
<polygon fill="black" stroke="black" points="409.49,-140.5 419.49,-137 409.49,-133.5 409.49,-140.5"/>
|
||||
</g>
|
||||
<!-- redis_state -->
|
||||
<g id="node5" class="node">
|
||||
<title>redis_state</title>
|
||||
<path fill="#ffcc80" stroke="black" d="M731.88,-155.84C731.88,-158.15 713.83,-160.03 691.62,-160.03 669.42,-160.03 651.38,-158.15 651.38,-155.84 651.38,-155.84 651.38,-118.16 651.38,-118.16 651.38,-115.85 669.42,-113.97 691.62,-113.97 713.83,-113.97 731.88,-115.85 731.88,-118.16 731.88,-118.16 731.88,-155.84 731.88,-155.84"/>
|
||||
<path fill="none" stroke="black" d="M731.88,-155.84C731.88,-153.53 713.83,-151.66 691.62,-151.66 669.42,-151.66 651.38,-153.53 651.38,-155.84"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="691.62" y="-140.25" font-family="Helvetica,sans-Serif" font-size="10.00">Redis</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="691.62" y="-127.5" font-family="Helvetica,sans-Serif" font-size="10.00">Current State</text>
|
||||
</g>
|
||||
<!-- validate->redis_state -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>validate->redis_state</title>
|
||||
<path fill="none" stroke="black" d="M555.47,-137C582.9,-137 614.22,-137 639.8,-137"/>
|
||||
<polygon fill="black" stroke="black" points="639.6,-140.5 649.6,-137 639.6,-133.5 639.6,-140.5"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="588.62" y="-139.7" font-family="Helvetica,sans-Serif" font-size="9.00">Upsert</text>
|
||||
</g>
|
||||
<!-- redis_pubsub -->
|
||||
<g id="node6" class="node">
|
||||
<title>redis_pubsub</title>
|
||||
<path fill="#ffb74d" stroke="black" d="M729.05,-78.12C729.05,-78.12 721.56,-87.24 721.56,-87.24 717.82,-91.79 708.18,-96.35 702.28,-96.35 702.28,-96.35 680.97,-96.35 680.97,-96.35 675.07,-96.35 665.43,-91.79 661.69,-87.24 661.69,-87.24 654.2,-78.12 654.2,-78.12 650.46,-73.56 650.46,-64.44 654.2,-59.88 654.2,-59.88 661.69,-50.76 661.69,-50.76 665.43,-46.21 675.07,-41.65 680.97,-41.65 680.97,-41.65 702.28,-41.65 702.28,-41.65 708.18,-41.65 717.82,-46.21 721.56,-50.76 721.56,-50.76 729.05,-59.88 729.05,-59.88 732.79,-64.44 732.79,-73.56 729.05,-78.12"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="691.62" y="-72.25" font-family="Helvetica,sans-Serif" font-size="10.00">Redis</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="691.62" y="-59.5" font-family="Helvetica,sans-Serif" font-size="10.00">Pub/Sub</text>
|
||||
</g>
|
||||
<!-- validate->redis_pubsub -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>validate->redis_pubsub</title>
|
||||
<path fill="none" stroke="black" d="M529.04,-123.57C562.44,-112.28 610.18,-96.17 645.1,-84.37"/>
|
||||
<polygon fill="black" stroke="black" points="646.17,-87.71 654.53,-81.19 643.93,-81.07 646.17,-87.71"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="588.62" y="-109.77" font-family="Helvetica,sans-Serif" font-size="9.00">Publish</text>
|
||||
</g>
|
||||
<!-- raw -->
|
||||
<g id="node7" class="node">
|
||||
<title>raw</title>
|
||||
<path fill="#f8bbd9" stroke="black" d="M729.62,-250.84C729.62,-253.15 712.59,-255.03 691.62,-255.03 670.66,-255.03 653.62,-253.15 653.62,-250.84 653.62,-250.84 653.62,-213.16 653.62,-213.16 653.62,-210.85 670.66,-208.97 691.62,-208.97 712.59,-208.97 729.62,-210.85 729.62,-213.16 729.62,-213.16 729.62,-250.84 729.62,-250.84"/>
|
||||
<path fill="none" stroke="black" d="M729.62,-250.84C729.62,-248.53 712.59,-246.66 691.62,-246.66 670.66,-246.66 653.62,-248.53 653.62,-250.84"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="691.62" y="-235.25" font-family="Helvetica,sans-Serif" font-size="10.00">metrics_raw</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="691.62" y="-222.5" font-family="Helvetica,sans-Serif" font-size="10.00">(5s, 24h)</text>
|
||||
</g>
|
||||
<!-- validate->raw -->
|
||||
<g id="edge6" class="edge">
|
||||
<title>validate->raw</title>
|
||||
<path fill="none" stroke="black" d="M523.01,-153.3C548.24,-165.44 583.6,-182.37 614.75,-197 623.81,-201.26 633.5,-205.76 642.83,-210.07"/>
|
||||
<polygon fill="black" stroke="black" points="641.22,-213.19 651.77,-214.2 644.16,-206.83 641.22,-213.19"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="588.62" y="-194.9" font-family="Helvetica,sans-Serif" font-size="9.00">Insert</text>
|
||||
</g>
|
||||
<!-- alerts -->
|
||||
<g id="node10" class="node">
|
||||
<title>alerts</title>
|
||||
<path fill="#c5cae9" stroke="black" d="M902.38,-106C902.38,-106 872.38,-106 872.38,-106 866.38,-106 860.38,-100 860.38,-94 860.38,-94 860.38,-82 860.38,-82 860.38,-76 866.38,-70 872.38,-70 872.38,-70 902.38,-70 902.38,-70 908.38,-70 914.38,-76 914.38,-82 914.38,-82 914.38,-94 914.38,-94 914.38,-100 908.38,-106 902.38,-106"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="887.38" y="-91.25" font-family="Helvetica,sans-Serif" font-size="10.00">Alert</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="887.38" y="-78.5" font-family="Helvetica,sans-Serif" font-size="10.00">Service</text>
|
||||
</g>
|
||||
<!-- redis_pubsub->alerts -->
|
||||
<g id="edge7" class="edge">
|
||||
<title>redis_pubsub->alerts</title>
|
||||
<path fill="none" stroke="black" d="M733.71,-73.03C767.65,-76.36 815.43,-81.04 848.46,-84.28"/>
|
||||
<polygon fill="black" stroke="black" points="848.11,-87.76 858.4,-85.26 848.79,-80.8 848.11,-87.76"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="805" y="-85.09" font-family="Helvetica,sans-Serif" font-size="9.00">metrics.*</text>
|
||||
</g>
|
||||
<!-- gateway -->
|
||||
<g id="node11" class="node">
|
||||
<title>gateway</title>
|
||||
<path fill="#9fa8da" stroke="black" d="M913.75,-52C913.75,-52 861,-52 861,-52 855,-52 849,-46 849,-40 849,-40 849,-28 849,-28 849,-22 855,-16 861,-16 861,-16 913.75,-16 913.75,-16 919.75,-16 925.75,-22 925.75,-28 925.75,-28 925.75,-40 925.75,-40 925.75,-46 919.75,-52 913.75,-52"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="887.38" y="-37.25" font-family="Helvetica,sans-Serif" font-size="10.00">Gateway</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="887.38" y="-24.5" font-family="Helvetica,sans-Serif" font-size="10.00">(WebSocket)</text>
|
||||
</g>
|
||||
<!-- redis_pubsub->gateway -->
|
||||
<g id="edge8" class="edge">
|
||||
<title>redis_pubsub->gateway</title>
|
||||
<path fill="none" stroke="black" d="M731.37,-62C761.89,-56.49 804.64,-48.77 837.51,-42.83"/>
|
||||
<polygon fill="black" stroke="black" points="837.98,-46.3 847.2,-41.08 836.74,-39.41 837.98,-46.3"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="805" y="-55.25" font-family="Helvetica,sans-Serif" font-size="9.00">metrics.*</text>
|
||||
</g>
|
||||
<!-- agg_1m -->
|
||||
<g id="node8" class="node">
|
||||
<title>agg_1m</title>
|
||||
<path fill="#f48fb1" stroke="black" d="M924.25,-250.84C924.25,-253.15 907.72,-255.03 887.38,-255.03 867.03,-255.03 850.5,-253.15 850.5,-250.84 850.5,-250.84 850.5,-213.16 850.5,-213.16 850.5,-210.85 867.03,-208.97 887.38,-208.97 907.72,-208.97 924.25,-210.85 924.25,-213.16 924.25,-213.16 924.25,-250.84 924.25,-250.84"/>
|
||||
<path fill="none" stroke="black" d="M924.25,-250.84C924.25,-248.53 907.72,-246.66 887.38,-246.66 867.03,-246.66 850.5,-248.53 850.5,-250.84"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="887.38" y="-235.25" font-family="Helvetica,sans-Serif" font-size="10.00">metrics_1m</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="887.38" y="-222.5" font-family="Helvetica,sans-Serif" font-size="10.00">(1m, 7d)</text>
|
||||
</g>
|
||||
<!-- raw->agg_1m -->
|
||||
<g id="edge9" class="edge">
|
||||
<title>raw->agg_1m</title>
|
||||
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M729.98,-232C760.97,-232 805.22,-232 838.74,-232"/>
|
||||
<polygon fill="black" stroke="black" points="838.6,-235.5 848.6,-232 838.6,-228.5 838.6,-235.5"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="805" y="-245.95" font-family="Helvetica,sans-Serif" font-size="9.00">Continuous</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="805" y="-234.7" font-family="Helvetica,sans-Serif" font-size="9.00">Aggregate</text>
|
||||
</g>
|
||||
<!-- lambda -->
|
||||
<g id="node12" class="node">
|
||||
<title>lambda</title>
|
||||
<path fill="#7986cb" stroke="black" stroke-dasharray="5,2" d="M910.38,-160C910.38,-160 864.38,-160 864.38,-160 858.38,-160 852.38,-154 852.38,-148 852.38,-148 852.38,-136 852.38,-136 852.38,-130 858.38,-124 864.38,-124 864.38,-124 910.38,-124 910.38,-124 916.38,-124 922.38,-130 922.38,-136 922.38,-136 922.38,-148 922.38,-148 922.38,-154 916.38,-160 910.38,-160"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="887.38" y="-145.25" font-family="Helvetica,sans-Serif" font-size="10.00">Lambda</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="887.38" y="-132.5" font-family="Helvetica,sans-Serif" font-size="10.00">Aggregator</text>
|
||||
</g>
|
||||
<!-- raw->lambda -->
|
||||
<g id="edge11" class="edge">
|
||||
<title>raw->lambda</title>
|
||||
<path fill="none" stroke="black" stroke-dasharray="1,5" d="M729.81,-215.18C742.43,-209.45 756.59,-202.98 769.5,-197 793.37,-185.95 819.91,-173.48 841.65,-163.21"/>
|
||||
<polygon fill="black" stroke="black" points="843,-166.44 850.54,-159.01 840,-160.12 843,-166.44"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="805" y="-205.05" font-family="Helvetica,sans-Serif" font-size="9.00">SQS</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="805" y="-193.8" font-family="Helvetica,sans-Serif" font-size="9.00">Trigger</text>
|
||||
</g>
|
||||
<!-- agg_1h -->
|
||||
<g id="node9" class="node">
|
||||
<title>agg_1h</title>
|
||||
<path fill="#ec407a" stroke="black" d="M1062.5,-250.84C1062.5,-253.15 1046.81,-255.03 1027.5,-255.03 1008.19,-255.03 992.5,-253.15 992.5,-250.84 992.5,-250.84 992.5,-213.16 992.5,-213.16 992.5,-210.85 1008.19,-208.97 1027.5,-208.97 1046.81,-208.97 1062.5,-210.85 1062.5,-213.16 1062.5,-213.16 1062.5,-250.84 1062.5,-250.84"/>
|
||||
<path fill="none" stroke="black" d="M1062.5,-250.84C1062.5,-248.53 1046.81,-246.66 1027.5,-246.66 1008.19,-246.66 992.5,-248.53 992.5,-250.84"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="1027.5" y="-235.25" font-family="Helvetica,sans-Serif" font-size="10.00">metrics_1h</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="1027.5" y="-222.5" font-family="Helvetica,sans-Serif" font-size="10.00">(1h, 90d)</text>
|
||||
</g>
|
||||
<!-- agg_1m->agg_1h -->
|
||||
<g id="edge10" class="edge">
|
||||
<title>agg_1m->agg_1h</title>
|
||||
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M924.67,-232C941.93,-232 962.74,-232 981.04,-232"/>
|
||||
<polygon fill="black" stroke="black" points="980.84,-235.5 990.84,-232 980.84,-228.5 980.84,-235.5"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="959.88" y="-245.95" font-family="Helvetica,sans-Serif" font-size="9.00">Hourly</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="959.88" y="-234.7" font-family="Helvetica,sans-Serif" font-size="9.00">Job</text>
|
||||
</g>
|
||||
<!-- lambda->agg_1m -->
|
||||
<g id="edge12" class="edge">
|
||||
<title>lambda->agg_1m</title>
|
||||
<path fill="none" stroke="black" stroke-dasharray="1,5" d="M887.38,-160.21C887.38,-170.91 887.38,-184.78 887.38,-197.47"/>
|
||||
<polygon fill="black" stroke="black" points="883.88,-197.16 887.38,-207.16 890.88,-197.16 883.88,-197.16"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="873.12" y="-187.18" font-family="Helvetica,sans-Serif" font-size="9.00">Batch</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="873.12" y="-175.93" font-family="Helvetica,sans-Serif" font-size="9.00">Write</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 17 KiB |
95
atlas/books/sysmonstm/architecture/03-deployment.dot
Normal file
@@ -0,0 +1,95 @@
|
||||
digraph Deployment {
|
||||
rankdir=TB;
|
||||
compound=true;
|
||||
fontname="Helvetica";
|
||||
node [fontname="Helvetica", fontsize=10];
|
||||
edge [fontname="Helvetica", fontsize=9];
|
||||
|
||||
labelloc="t";
|
||||
label="Deployment Architecture";
|
||||
fontsize=14;
|
||||
|
||||
node [shape=box, style="rounded,filled"];
|
||||
|
||||
// Local Development
|
||||
subgraph cluster_local {
|
||||
label="Local Development";
|
||||
style=filled;
|
||||
fillcolor="#E3F2FD";
|
||||
|
||||
subgraph cluster_kind {
|
||||
label="Kind Cluster";
|
||||
style=filled;
|
||||
fillcolor="#BBDEFB";
|
||||
|
||||
tilt [label="Tilt\n(Live Reload)", shape=component, fillcolor="#90CAF9"];
|
||||
k8s_local [label="K8s Pods\n(via Kustomize)", fillcolor="#64B5F6"];
|
||||
}
|
||||
|
||||
compose [label="Docker Compose\n(Alternative)", fillcolor="#90CAF9", style="rounded,dashed"];
|
||||
}
|
||||
|
||||
// AWS Staging/Demo
|
||||
subgraph cluster_aws {
|
||||
label="AWS (sysmonstm.mcrn.ar)";
|
||||
style=filled;
|
||||
fillcolor="#E8F5E9";
|
||||
|
||||
subgraph cluster_ec2 {
|
||||
label="EC2 t2.small";
|
||||
style=filled;
|
||||
fillcolor="#C8E6C9";
|
||||
|
||||
compose_ec2 [label="Docker Compose\n(All Services)", fillcolor="#A5D6A7"];
|
||||
nginx [label="Nginx\n(SSL Termination)", fillcolor="#81C784"];
|
||||
}
|
||||
|
||||
subgraph cluster_lambda {
|
||||
label="Lambda (Data Processing)";
|
||||
style=filled;
|
||||
fillcolor="#DCEDC8";
|
||||
|
||||
lambda_agg [label="Aggregator\nLambda", fillcolor="#AED581"];
|
||||
lambda_compact [label="Compactor\nLambda", fillcolor="#9CCC65"];
|
||||
}
|
||||
|
||||
sqs [label="SQS\n(Buffer)", shape=hexagon, fillcolor="#FFE082"];
|
||||
s3 [label="S3\n(Backup)", shape=cylinder, fillcolor="#FFE082"];
|
||||
}
|
||||
|
||||
// CI/CD
|
||||
subgraph cluster_cicd {
|
||||
label="CI/CD";
|
||||
style=filled;
|
||||
fillcolor="#F3E5F5";
|
||||
|
||||
woodpecker [label="Woodpecker CI", fillcolor="#CE93D8"];
|
||||
registry [label="Container\nRegistry", shape=cylinder, fillcolor="#BA68C8"];
|
||||
}
|
||||
|
||||
// Collectors (External)
|
||||
subgraph cluster_collectors {
|
||||
label="Monitored Machines";
|
||||
style=dashed;
|
||||
color=gray;
|
||||
|
||||
coll1 [label="Collector\n(Machine 1)", fillcolor="#FFCCBC"];
|
||||
coll2 [label="Collector\n(Machine 2)", fillcolor="#FFCCBC"];
|
||||
coll3 [label="Collector\n(Machine N)", fillcolor="#FFCCBC"];
|
||||
}
|
||||
|
||||
// Connections
|
||||
tilt -> k8s_local [style=invis];
|
||||
woodpecker -> registry [label="Push"];
|
||||
registry -> compose_ec2 [label="Pull"];
|
||||
registry -> k8s_local [label="Pull", style=dashed];
|
||||
|
||||
nginx -> compose_ec2 [label="Proxy"];
|
||||
compose_ec2 -> sqs [label="Events"];
|
||||
sqs -> lambda_agg [label="Trigger"];
|
||||
lambda_compact -> s3 [label="Archive"];
|
||||
|
||||
coll1 -> compose_ec2 [label="gRPC", lhead=cluster_ec2];
|
||||
coll2 -> compose_ec2 [label="gRPC", lhead=cluster_ec2];
|
||||
coll3 -> compose_ec2 [label="gRPC", lhead=cluster_ec2];
|
||||
}
|
||||
221
atlas/books/sysmonstm/architecture/03-deployment.svg
Normal file
@@ -0,0 +1,221 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 14.1.1 (0)
|
||||
-->
|
||||
<!-- Title: Deployment Pages: 1 -->
|
||||
<svg width="872pt" height="662pt"
|
||||
viewBox="0.00 0.00 872.00 662.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 658.3)">
|
||||
<title>Deployment</title>
|
||||
<polygon fill="white" stroke="none" points="-4,4 -4,-658.3 868,-658.3 868,4 -4,4"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="432" y="-637" font-family="Helvetica,sans-Serif" font-size="14.00">Deployment Architecture</text>
|
||||
<g id="clust1" class="cluster">
|
||||
<title>cluster_local</title>
|
||||
<polygon fill="#e3f2fd" stroke="black" points="8,-307.77 8,-514.55 238,-514.55 238,-307.77 8,-307.77"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="123" y="-497.25" font-family="Helvetica,sans-Serif" font-size="14.00">Local Development</text>
|
||||
</g>
|
||||
<g id="clust2" class="cluster">
|
||||
<title>cluster_kind</title>
|
||||
<polygon fill="#bbdefb" stroke="black" points="16,-315.77 16,-481.3 124,-481.3 124,-315.77 16,-315.77"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="70" y="-464" font-family="Helvetica,sans-Serif" font-size="14.00">Kind Cluster</text>
|
||||
</g>
|
||||
<g id="clust3" class="cluster">
|
||||
<title>cluster_aws</title>
|
||||
<polygon fill="#e8f5e9" stroke="black" points="642,-8 642,-514.55 856,-514.55 856,-8 642,-8"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="749" y="-497.25" font-family="Helvetica,sans-Serif" font-size="14.00">AWS (sysmonstm.mcrn.ar)</text>
|
||||
</g>
|
||||
<g id="clust4" class="cluster">
|
||||
<title>cluster_ec2</title>
|
||||
<polygon fill="#c8e6c9" stroke="black" points="650,-315.77 650,-481.3 768,-481.3 768,-315.77 650,-315.77"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="709" y="-464" font-family="Helvetica,sans-Serif" font-size="14.00">EC2 t2.small</text>
|
||||
</g>
|
||||
<g id="clust5" class="cluster">
|
||||
<title>cluster_lambda</title>
|
||||
<polygon fill="#dcedc8" stroke="black" points="650,-101.31 650,-178.56 848,-178.56 848,-101.31 650,-101.31"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="749" y="-161.26" font-family="Helvetica,sans-Serif" font-size="14.00">Lambda (Data Processing)</text>
|
||||
</g>
|
||||
<g id="clust6" class="cluster">
|
||||
<title>cluster_cicd</title>
|
||||
<polygon fill="#f3e5f5" stroke="black" points="246,-399.02 246,-621.05 350,-621.05 350,-399.02 246,-399.02"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="298" y="-603.75" font-family="Helvetica,sans-Serif" font-size="14.00">CI/CD</text>
|
||||
</g>
|
||||
<g id="clust7" class="cluster">
|
||||
<title>cluster_collectors</title>
|
||||
<polygon fill="none" stroke="gray" stroke-dasharray="5,2" points="358,-404.05 358,-481.3 634,-481.3 634,-404.05 358,-404.05"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="496" y="-464" font-family="Helvetica,sans-Serif" font-size="14.00">Monitored Machines</text>
|
||||
</g>
|
||||
<!-- tilt -->
|
||||
<g id="node1" class="node">
|
||||
<title>tilt</title>
|
||||
<polygon fill="#90caf9" stroke="black" points="110.25,-448.05 29.75,-448.05 29.75,-444.05 25.75,-444.05 25.75,-440.05 29.75,-440.05 29.75,-420.05 25.75,-420.05 25.75,-416.05 29.75,-416.05 29.75,-412.05 110.25,-412.05 110.25,-448.05"/>
|
||||
<polyline fill="none" stroke="black" points="29.75,-444.05 33.75,-444.05 33.75,-440.05 29.75,-440.05"/>
|
||||
<polyline fill="none" stroke="black" points="29.75,-420.05 33.75,-420.05 33.75,-416.05 29.75,-416.05"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="70" y="-433.3" font-family="Helvetica,sans-Serif" font-size="10.00">Tilt</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="70" y="-420.55" font-family="Helvetica,sans-Serif" font-size="10.00">(Live Reload)</text>
|
||||
</g>
|
||||
<!-- k8s_local -->
|
||||
<g id="node2" class="node">
|
||||
<title>k8s_local</title>
|
||||
<path fill="#64b5f6" stroke="black" d="M104.25,-359.77C104.25,-359.77 35.75,-359.77 35.75,-359.77 29.75,-359.77 23.75,-353.77 23.75,-347.77 23.75,-347.77 23.75,-335.77 23.75,-335.77 23.75,-329.77 29.75,-323.77 35.75,-323.77 35.75,-323.77 104.25,-323.77 104.25,-323.77 110.25,-323.77 116.25,-329.77 116.25,-335.77 116.25,-335.77 116.25,-347.77 116.25,-347.77 116.25,-353.77 110.25,-359.77 104.25,-359.77"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="70" y="-345.02" font-family="Helvetica,sans-Serif" font-size="10.00">K8s Pods</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="70" y="-332.27" font-family="Helvetica,sans-Serif" font-size="10.00">(via Kustomize)</text>
|
||||
</g>
|
||||
<!-- tilt->k8s_local -->
|
||||
<!-- compose -->
|
||||
<g id="node3" class="node">
|
||||
<title>compose</title>
|
||||
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M218.25,-448.05C218.25,-448.05 143.75,-448.05 143.75,-448.05 137.75,-448.05 131.75,-442.05 131.75,-436.05 131.75,-436.05 131.75,-424.05 131.75,-424.05 131.75,-418.05 137.75,-412.05 143.75,-412.05 143.75,-412.05 218.25,-412.05 218.25,-412.05 224.25,-412.05 230.25,-418.05 230.25,-424.05 230.25,-424.05 230.25,-436.05 230.25,-436.05 230.25,-442.05 224.25,-448.05 218.25,-448.05"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="181" y="-433.3" font-family="Helvetica,sans-Serif" font-size="10.00">Docker Compose</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="181" y="-420.55" font-family="Helvetica,sans-Serif" font-size="10.00">(Alternative)</text>
|
||||
</g>
|
||||
<!-- compose_ec2 -->
|
||||
<g id="node4" class="node">
|
||||
<title>compose_ec2</title>
|
||||
<path fill="#a5d6a7" stroke="black" d="M744.25,-359.77C744.25,-359.77 669.75,-359.77 669.75,-359.77 663.75,-359.77 657.75,-353.77 657.75,-347.77 657.75,-347.77 657.75,-335.77 657.75,-335.77 657.75,-329.77 663.75,-323.77 669.75,-323.77 669.75,-323.77 744.25,-323.77 744.25,-323.77 750.25,-323.77 756.25,-329.77 756.25,-335.77 756.25,-335.77 756.25,-347.77 756.25,-347.77 756.25,-353.77 750.25,-359.77 744.25,-359.77"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="707" y="-345.02" font-family="Helvetica,sans-Serif" font-size="10.00">Docker Compose</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="707" y="-332.27" font-family="Helvetica,sans-Serif" font-size="10.00">(All Services)</text>
|
||||
</g>
|
||||
<!-- sqs -->
|
||||
<g id="node8" class="node">
|
||||
<title>sqs</title>
|
||||
<path fill="#ffe082" stroke="black" d="M742.89,-252.28C742.89,-252.28 735.71,-261.4 735.71,-261.4 732.12,-265.96 722.73,-270.52 716.93,-270.52 716.93,-270.52 697.07,-270.52 697.07,-270.52 691.27,-270.52 681.88,-265.96 678.29,-261.4 678.29,-261.4 671.11,-252.28 671.11,-252.28 667.52,-247.72 667.52,-238.61 671.11,-234.05 671.11,-234.05 678.29,-224.93 678.29,-224.93 681.88,-220.37 691.27,-215.81 697.07,-215.81 697.07,-215.81 716.93,-215.81 716.93,-215.81 722.73,-215.81 732.12,-220.37 735.71,-224.93 735.71,-224.93 742.89,-234.05 742.89,-234.05 746.48,-238.61 746.48,-247.72 742.89,-252.28"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="707" y="-246.42" font-family="Helvetica,sans-Serif" font-size="10.00">SQS</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="707" y="-233.67" font-family="Helvetica,sans-Serif" font-size="10.00">(Buffer)</text>
|
||||
</g>
|
||||
<!-- compose_ec2->sqs -->
|
||||
<g id="edge6" class="edge">
|
||||
<title>compose_ec2->sqs</title>
|
||||
<path fill="none" stroke="black" d="M707,-323.5C707,-311.94 707,-296.26 707,-281.89"/>
|
||||
<polygon fill="black" stroke="black" points="710.5,-282.27 707,-272.27 703.5,-282.27 710.5,-282.27"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="722.38" y="-291.22" font-family="Helvetica,sans-Serif" font-size="9.00">Events</text>
|
||||
</g>
|
||||
<!-- nginx -->
|
||||
<g id="node5" class="node">
|
||||
<title>nginx</title>
|
||||
<path fill="#81c784" stroke="black" d="M747.75,-448.05C747.75,-448.05 670.25,-448.05 670.25,-448.05 664.25,-448.05 658.25,-442.05 658.25,-436.05 658.25,-436.05 658.25,-424.05 658.25,-424.05 658.25,-418.05 664.25,-412.05 670.25,-412.05 670.25,-412.05 747.75,-412.05 747.75,-412.05 753.75,-412.05 759.75,-418.05 759.75,-424.05 759.75,-424.05 759.75,-436.05 759.75,-436.05 759.75,-442.05 753.75,-448.05 747.75,-448.05"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="709" y="-433.3" font-family="Helvetica,sans-Serif" font-size="10.00">Nginx</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="709" y="-420.55" font-family="Helvetica,sans-Serif" font-size="10.00">(SSL Termination)</text>
|
||||
</g>
|
||||
<!-- nginx->compose_ec2 -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>nginx->compose_ec2</title>
|
||||
<path fill="none" stroke="black" d="M708.6,-411.59C708.33,-400.13 707.98,-384.86 707.67,-371.63"/>
|
||||
<polygon fill="black" stroke="black" points="711.17,-371.63 707.44,-361.72 704.17,-371.79 711.17,-371.63"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="720.43" y="-380.47" font-family="Helvetica,sans-Serif" font-size="9.00">Proxy</text>
|
||||
</g>
|
||||
<!-- lambda_agg -->
|
||||
<g id="node6" class="node">
|
||||
<title>lambda_agg</title>
|
||||
<path fill="#aed581" stroke="black" d="M730,-145.31C730,-145.31 684,-145.31 684,-145.31 678,-145.31 672,-139.31 672,-133.31 672,-133.31 672,-121.31 672,-121.31 672,-115.31 678,-109.31 684,-109.31 684,-109.31 730,-109.31 730,-109.31 736,-109.31 742,-115.31 742,-121.31 742,-121.31 742,-133.31 742,-133.31 742,-139.31 736,-145.31 730,-145.31"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="707" y="-130.56" font-family="Helvetica,sans-Serif" font-size="10.00">Aggregator</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="707" y="-117.81" font-family="Helvetica,sans-Serif" font-size="10.00">Lambda</text>
|
||||
</g>
|
||||
<!-- lambda_compact -->
|
||||
<g id="node7" class="node">
|
||||
<title>lambda_compact</title>
|
||||
<path fill="#9ccc65" stroke="black" d="M822.62,-145.31C822.62,-145.31 777.38,-145.31 777.38,-145.31 771.38,-145.31 765.38,-139.31 765.38,-133.31 765.38,-133.31 765.38,-121.31 765.38,-121.31 765.38,-115.31 771.38,-109.31 777.38,-109.31 777.38,-109.31 822.62,-109.31 822.62,-109.31 828.62,-109.31 834.62,-115.31 834.62,-121.31 834.62,-121.31 834.62,-133.31 834.62,-133.31 834.62,-139.31 828.62,-145.31 822.62,-145.31"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="800" y="-130.56" font-family="Helvetica,sans-Serif" font-size="10.00">Compactor</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="800" y="-117.81" font-family="Helvetica,sans-Serif" font-size="10.00">Lambda</text>
|
||||
</g>
|
||||
<!-- s3 -->
|
||||
<g id="node9" class="node">
|
||||
<title>s3</title>
|
||||
<path fill="#ffe082" stroke="black" d="M829.38,-57.88C829.38,-60.19 816.21,-62.06 800,-62.06 783.79,-62.06 770.62,-60.19 770.62,-57.88 770.62,-57.88 770.62,-20.19 770.62,-20.19 770.62,-17.88 783.79,-16 800,-16 816.21,-16 829.38,-17.88 829.38,-20.19 829.38,-20.19 829.38,-57.88 829.38,-57.88"/>
|
||||
<path fill="none" stroke="black" d="M829.38,-57.88C829.38,-55.56 816.21,-53.69 800,-53.69 783.79,-53.69 770.62,-55.56 770.62,-57.88"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="800" y="-42.28" font-family="Helvetica,sans-Serif" font-size="10.00">S3</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="800" y="-29.53" font-family="Helvetica,sans-Serif" font-size="10.00">(Backup)</text>
|
||||
</g>
|
||||
<!-- lambda_compact->s3 -->
|
||||
<g id="edge8" class="edge">
|
||||
<title>lambda_compact->s3</title>
|
||||
<path fill="none" stroke="black" d="M800,-108.85C800,-98.81 800,-85.84 800,-73.88"/>
|
||||
<polygon fill="black" stroke="black" points="803.5,-73.9 800,-63.9 796.5,-73.9 803.5,-73.9"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="816.88" y="-82.76" font-family="Helvetica,sans-Serif" font-size="9.00">Archive</text>
|
||||
</g>
|
||||
<!-- sqs->lambda_agg -->
|
||||
<g id="edge7" class="edge">
|
||||
<title>sqs->lambda_agg</title>
|
||||
<path fill="none" stroke="black" d="M707,-215.47C707,-197.96 707,-175.06 707,-157.13"/>
|
||||
<polygon fill="black" stroke="black" points="710.5,-157.15 707,-147.15 703.5,-157.15 710.5,-157.15"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="722.75" y="-189.26" font-family="Helvetica,sans-Serif" font-size="9.00">Trigger</text>
|
||||
</g>
|
||||
<!-- woodpecker -->
|
||||
<g id="node10" class="node">
|
||||
<title>woodpecker</title>
|
||||
<path fill="#ce93d8" stroke="black" d="M330,-587.8C330,-587.8 266,-587.8 266,-587.8 260,-587.8 254,-581.8 254,-575.8 254,-575.8 254,-563.8 254,-563.8 254,-557.8 260,-551.8 266,-551.8 266,-551.8 330,-551.8 330,-551.8 336,-551.8 342,-557.8 342,-563.8 342,-563.8 342,-575.8 342,-575.8 342,-581.8 336,-587.8 330,-587.8"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="298" y="-566.67" font-family="Helvetica,sans-Serif" font-size="10.00">Woodpecker CI</text>
|
||||
</g>
|
||||
<!-- registry -->
|
||||
<g id="node11" class="node">
|
||||
<title>registry</title>
|
||||
<path fill="#ba68c8" stroke="black" d="M329.62,-448.89C329.62,-451.2 315.45,-453.08 298,-453.08 280.55,-453.08 266.38,-451.2 266.38,-448.89 266.38,-448.89 266.38,-411.21 266.38,-411.21 266.38,-408.89 280.55,-407.02 298,-407.02 315.45,-407.02 329.62,-408.89 329.62,-411.21 329.62,-411.21 329.62,-448.89 329.62,-448.89"/>
|
||||
<path fill="none" stroke="black" d="M329.62,-448.89C329.62,-446.58 315.45,-444.71 298,-444.71 280.55,-444.71 266.38,-446.58 266.38,-448.89"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="298" y="-433.3" font-family="Helvetica,sans-Serif" font-size="10.00">Container</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="298" y="-420.55" font-family="Helvetica,sans-Serif" font-size="10.00">Registry</text>
|
||||
</g>
|
||||
<!-- woodpecker->registry -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>woodpecker->registry</title>
|
||||
<path fill="none" stroke="black" d="M298,-551.35C298,-529.66 298,-492.15 298,-464.77"/>
|
||||
<polygon fill="black" stroke="black" points="301.5,-464.88 298,-454.88 294.5,-464.88 301.5,-464.88"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="308.88" y="-525.25" font-family="Helvetica,sans-Serif" font-size="9.00">Push</text>
|
||||
</g>
|
||||
<!-- registry->k8s_local -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>registry->k8s_local</title>
|
||||
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M265.9,-410.59C258.2,-406.51 249.91,-402.4 242,-399.02 204.6,-383.02 161.03,-368.81 127.1,-358.68"/>
|
||||
<polygon fill="black" stroke="black" points="128.47,-355.44 117.89,-355.97 126.49,-362.15 128.47,-355.44"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="222.42" y="-380.47" font-family="Helvetica,sans-Serif" font-size="9.00">Pull</text>
|
||||
</g>
|
||||
<!-- registry->compose_ec2 -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>registry->compose_ec2</title>
|
||||
<path fill="none" stroke="black" d="M329.84,-409.93C337.55,-405.88 345.91,-401.95 354,-399.02 452.44,-363.35 574.46,-350.26 646.22,-345.49"/>
|
||||
<polygon fill="black" stroke="black" points="646.02,-349.01 655.78,-344.88 645.58,-342.02 646.02,-349.01"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="427.09" y="-380.47" font-family="Helvetica,sans-Serif" font-size="9.00">Pull</text>
|
||||
</g>
|
||||
<!-- coll1 -->
|
||||
<g id="node12" class="node">
|
||||
<title>coll1</title>
|
||||
<path fill="#ffccbc" stroke="black" d="M521.88,-448.05C521.88,-448.05 472.12,-448.05 472.12,-448.05 466.12,-448.05 460.12,-442.05 460.12,-436.05 460.12,-436.05 460.12,-424.05 460.12,-424.05 460.12,-418.05 466.12,-412.05 472.12,-412.05 472.12,-412.05 521.88,-412.05 521.88,-412.05 527.88,-412.05 533.88,-418.05 533.88,-424.05 533.88,-424.05 533.88,-436.05 533.88,-436.05 533.88,-442.05 527.88,-448.05 521.88,-448.05"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="497" y="-433.3" font-family="Helvetica,sans-Serif" font-size="10.00">Collector</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="497" y="-420.55" font-family="Helvetica,sans-Serif" font-size="10.00">(Machine 1)</text>
|
||||
</g>
|
||||
<!-- coll1->compose_ec2 -->
|
||||
<g id="edge9" class="edge">
|
||||
<title>coll1->compose_ec2</title>
|
||||
<path fill="none" stroke="black" d="M521.16,-411.67C528.02,-407.19 535.63,-402.62 543,-399.02 576.02,-382.89 614.85,-369.35 646.44,-359.6"/>
|
||||
<polygon fill="black" stroke="black" points="640.37,-365.52 648.58,-358.82 637.98,-358.94 640.37,-365.52"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="602.75" y="-380.47" font-family="Helvetica,sans-Serif" font-size="9.00">gRPC</text>
|
||||
</g>
|
||||
<!-- coll2 -->
|
||||
<g id="node13" class="node">
|
||||
<title>coll2</title>
|
||||
<path fill="#ffccbc" stroke="black" d="M613.88,-448.05C613.88,-448.05 564.12,-448.05 564.12,-448.05 558.12,-448.05 552.12,-442.05 552.12,-436.05 552.12,-436.05 552.12,-424.05 552.12,-424.05 552.12,-418.05 558.12,-412.05 564.12,-412.05 564.12,-412.05 613.88,-412.05 613.88,-412.05 619.88,-412.05 625.88,-418.05 625.88,-424.05 625.88,-424.05 625.88,-436.05 625.88,-436.05 625.88,-442.05 619.88,-448.05 613.88,-448.05"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="589" y="-433.3" font-family="Helvetica,sans-Serif" font-size="10.00">Collector</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="589" y="-420.55" font-family="Helvetica,sans-Serif" font-size="10.00">(Machine 2)</text>
|
||||
</g>
|
||||
<!-- coll2->compose_ec2 -->
|
||||
<g id="edge10" class="edge">
|
||||
<title>coll2->compose_ec2</title>
|
||||
<path fill="none" stroke="black" d="M612.88,-411.59C621.13,-405.55 630.83,-398.47 640.8,-391.17"/>
|
||||
<polygon fill="black" stroke="black" points="642.77,-394.07 648.78,-385.34 638.64,-388.41 642.77,-394.07"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="670.19" y="-380.47" font-family="Helvetica,sans-Serif" font-size="9.00">gRPC</text>
|
||||
</g>
|
||||
<!-- coll3 -->
|
||||
<g id="node14" class="node">
|
||||
<title>coll3</title>
|
||||
<path fill="#ffccbc" stroke="black" d="M429.62,-448.05C429.62,-448.05 378.38,-448.05 378.38,-448.05 372.38,-448.05 366.38,-442.05 366.38,-436.05 366.38,-436.05 366.38,-424.05 366.38,-424.05 366.38,-418.05 372.38,-412.05 378.38,-412.05 378.38,-412.05 429.62,-412.05 429.62,-412.05 435.62,-412.05 441.62,-418.05 441.62,-424.05 441.62,-424.05 441.62,-436.05 441.62,-436.05 441.62,-442.05 435.62,-448.05 429.62,-448.05"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="404" y="-433.3" font-family="Helvetica,sans-Serif" font-size="10.00">Collector</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="404" y="-420.55" font-family="Helvetica,sans-Serif" font-size="10.00">(Machine N)</text>
|
||||
</g>
|
||||
<!-- coll3->compose_ec2 -->
|
||||
<g id="edge11" class="edge">
|
||||
<title>coll3->compose_ec2</title>
|
||||
<path fill="none" stroke="black" d="M427.53,-411.82C434.78,-407.12 442.97,-402.41 451,-399.02 514.86,-372.07 593.36,-357.28 646.47,-349.71"/>
|
||||
<polygon fill="black" stroke="black" points="639.16,-354.39 648.5,-349.4 638.08,-347.48 639.16,-354.39"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="516.54" y="-380.47" font-family="Helvetica,sans-Serif" font-size="9.00">gRPC</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 18 KiB |
67
atlas/books/sysmonstm/architecture/04-grpc-services.dot
Normal file
@@ -0,0 +1,67 @@
|
||||
digraph GrpcServices {
|
||||
rankdir=LR;
|
||||
compound=true;
|
||||
fontname="Helvetica";
|
||||
node [fontname="Helvetica", fontsize=10];
|
||||
edge [fontname="Helvetica", fontsize=9];
|
||||
|
||||
labelloc="t";
|
||||
label="gRPC Service Definitions";
|
||||
fontsize=14;
|
||||
|
||||
node [shape=record, style=filled];
|
||||
|
||||
// MetricsService
|
||||
subgraph cluster_metrics {
|
||||
label="MetricsService";
|
||||
style=filled;
|
||||
fillcolor="#E8F5E9";
|
||||
|
||||
metrics_svc [label="{MetricsService|+ StreamMetrics(stream Metric) → StreamAck\l+ GetCurrentState(StateRequest) → MachineState\l+ GetAllStates(Empty) → AllMachinesState\l}", fillcolor="#C8E6C9"];
|
||||
|
||||
metric_msg [label="{Metric|machine_id: string\lhostname: string\ltimestamp_ms: int64\ltype: MetricType\lvalue: double\llabels: map\l}", fillcolor="#A5D6A7"];
|
||||
|
||||
machine_state [label="{MachineState|machine_id: string\lhostname: string\llast_seen_ms: int64\lcurrent_metrics: Metric[]\lhealth: HealthStatus\lmetadata: map\l}", fillcolor="#A5D6A7"];
|
||||
}
|
||||
|
||||
// ControlService
|
||||
subgraph cluster_control {
|
||||
label="ControlService";
|
||||
style=filled;
|
||||
fillcolor="#E3F2FD";
|
||||
|
||||
control_svc [label="{ControlService|+ Control(stream Command) → stream Response\l}", fillcolor="#90CAF9"];
|
||||
|
||||
commands [label="{ControlCommand|command_id: string\l|UpdateIntervalCommand\lRestartCollectionCommand\lShutdownCommand\l}", fillcolor="#64B5F6"];
|
||||
}
|
||||
|
||||
// ConfigService
|
||||
subgraph cluster_config {
|
||||
label="ConfigService";
|
||||
style=filled;
|
||||
fillcolor="#FFF3E0";
|
||||
|
||||
config_svc [label="{ConfigService|+ GetConfig(ConfigRequest) → CollectorConfig\l+ WatchConfig(ConfigRequest) → stream CollectorConfig\l}", fillcolor="#FFE0B2"];
|
||||
|
||||
collector_config [label="{CollectorConfig|collection_interval_seconds: int32\lenabled_metrics: MetricType[]\llabels: map\lthresholds: ThresholdConfig[]\l}", fillcolor="#FFCC80"];
|
||||
}
|
||||
|
||||
// Enums
|
||||
subgraph cluster_enums {
|
||||
label="Enums";
|
||||
style=filled;
|
||||
fillcolor="#F3E5F5";
|
||||
|
||||
metric_type [label="{MetricType|CPU_PERCENT\lMEMORY_PERCENT\lDISK_PERCENT\lNETWORK_*\lLOAD_AVG_*\l...}", fillcolor="#E1BEE7"];
|
||||
|
||||
health_status [label="{HealthStatus|HEALTHY\lWARNING\lCRITICAL\lUNKNOWN\lOFFLINE\l}", fillcolor="#CE93D8"];
|
||||
}
|
||||
|
||||
// Relationships
|
||||
metrics_svc -> metric_msg [style=dashed];
|
||||
metrics_svc -> machine_state [style=dashed];
|
||||
control_svc -> commands [style=dashed];
|
||||
config_svc -> collector_config [style=dashed];
|
||||
metric_msg -> metric_type [style=dotted];
|
||||
machine_state -> health_status [style=dotted];
|
||||
}
|
||||
171
atlas/books/sysmonstm/architecture/04-grpc-services.svg
Normal file
@@ -0,0 +1,171 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 14.1.1 (0)
|
||||
-->
|
||||
<!-- Title: GrpcServices Pages: 1 -->
|
||||
<svg width="1030pt" height="486pt"
|
||||
viewBox="0.00 0.00 1030.00 486.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 482.25)">
|
||||
<title>GrpcServices</title>
|
||||
<polygon fill="white" stroke="none" points="-4,4 -4,-482.25 1026.25,-482.25 1026.25,4 -4,4"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="511.12" y="-460.95" font-family="Helvetica,sans-Serif" font-size="14.00">gRPC Service Definitions</text>
|
||||
<g id="clust1" class="cluster">
|
||||
<title>cluster_metrics</title>
|
||||
<polygon fill="#e8f5e9" stroke="black" points="21.5,-8 21.5,-239 726.75,-239 726.75,-8 21.5,-8"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="374.12" y="-221.7" font-family="Helvetica,sans-Serif" font-size="14.00">MetricsService</text>
|
||||
</g>
|
||||
<g id="clust2" class="cluster">
|
||||
<title>cluster_control</title>
|
||||
<polygon fill="#e3f2fd" stroke="black" points="23.38,-247 23.38,-336 799.25,-336 799.25,-247 23.38,-247"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="411.31" y="-318.7" font-family="Helvetica,sans-Serif" font-size="14.00">ControlService</text>
|
||||
</g>
|
||||
<g id="clust3" class="cluster">
|
||||
<title>cluster_config</title>
|
||||
<polygon fill="#fff3e0" stroke="black" points="8,-344 8,-445 753,-445 753,-344 8,-344"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="380.5" y="-427.7" font-family="Helvetica,sans-Serif" font-size="14.00">ConfigService</text>
|
||||
</g>
|
||||
<g id="clust4" class="cluster">
|
||||
<title>cluster_enums</title>
|
||||
<polygon fill="#f3e5f5" stroke="black" points="819.25,-11 819.25,-229 1014.25,-229 1014.25,-11 819.25,-11"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="916.75" y="-211.7" font-family="Helvetica,sans-Serif" font-size="14.00">Enums</text>
|
||||
</g>
|
||||
<!-- metrics_svc -->
|
||||
<g id="node1" class="node">
|
||||
<title>metrics_svc</title>
|
||||
<polygon fill="#c8e6c9" stroke="black" points="29.5,-87.88 29.5,-134.12 377.25,-134.12 377.25,-87.88 29.5,-87.88"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="73.5" y="-107.88" font-family="Helvetica,sans-Serif" font-size="10.00">MetricsService</text>
|
||||
<polyline fill="none" stroke="black" points="117.5,-87.88 117.5,-134.12"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="125.5" y="-120.62" font-family="Helvetica,sans-Serif" font-size="10.00">+ StreamMetrics(stream Metric) → StreamAck</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="125.5" y="-107.88" font-family="Helvetica,sans-Serif" font-size="10.00">+ GetCurrentState(StateRequest) → MachineState</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="125.5" y="-95.12" font-family="Helvetica,sans-Serif" font-size="10.00">+ GetAllStates(Empty) → AllMachinesState</text>
|
||||
</g>
|
||||
<!-- metric_msg -->
|
||||
<g id="node2" class="node">
|
||||
<title>metric_msg</title>
|
||||
<polygon fill="#a5d6a7" stroke="black" points="525.5,-16.75 525.5,-101.25 692.5,-101.25 692.5,-16.75 525.5,-16.75"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="548.88" y="-55.88" font-family="Helvetica,sans-Serif" font-size="10.00">Metric</text>
|
||||
<polyline fill="none" stroke="black" points="572.25,-16.75 572.25,-101.25"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="580.25" y="-87.75" font-family="Helvetica,sans-Serif" font-size="10.00">machine_id: string</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="580.25" y="-75" font-family="Helvetica,sans-Serif" font-size="10.00">hostname: string</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="580.25" y="-62.25" font-family="Helvetica,sans-Serif" font-size="10.00">timestamp_ms: int64</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="580.25" y="-49.5" font-family="Helvetica,sans-Serif" font-size="10.00">type: MetricType</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="580.25" y="-36.75" font-family="Helvetica,sans-Serif" font-size="10.00">value: double</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="580.25" y="-24" font-family="Helvetica,sans-Serif" font-size="10.00">labels: map</text>
|
||||
</g>
|
||||
<!-- metrics_svc->metric_msg -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>metrics_svc->metric_msg</title>
|
||||
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M377.6,-88.68C424.41,-82.65 473.31,-76.35 513.96,-71.12"/>
|
||||
<polygon fill="black" stroke="black" points="514.22,-74.61 523.69,-69.86 513.33,-67.67 514.22,-74.61"/>
|
||||
</g>
|
||||
<!-- machine_state -->
|
||||
<g id="node3" class="node">
|
||||
<title>machine_state</title>
|
||||
<polygon fill="#a5d6a7" stroke="black" points="499.25,-120.75 499.25,-205.25 718.75,-205.25 718.75,-120.75 499.25,-120.75"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="540.62" y="-159.88" font-family="Helvetica,sans-Serif" font-size="10.00">MachineState</text>
|
||||
<polyline fill="none" stroke="black" points="582,-120.75 582,-205.25"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="590" y="-191.75" font-family="Helvetica,sans-Serif" font-size="10.00">machine_id: string</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="590" y="-179" font-family="Helvetica,sans-Serif" font-size="10.00">hostname: string</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="590" y="-166.25" font-family="Helvetica,sans-Serif" font-size="10.00">last_seen_ms: int64</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="590" y="-153.5" font-family="Helvetica,sans-Serif" font-size="10.00">current_metrics: Metric[]</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="590" y="-140.75" font-family="Helvetica,sans-Serif" font-size="10.00">health: HealthStatus</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="590" y="-128" font-family="Helvetica,sans-Serif" font-size="10.00">metadata: map</text>
|
||||
</g>
|
||||
<!-- metrics_svc->machine_state -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>metrics_svc->machine_state</title>
|
||||
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M377.6,-133.32C414.74,-138.1 453.2,-143.06 487.8,-147.51"/>
|
||||
<polygon fill="black" stroke="black" points="487.03,-150.94 497.4,-148.75 487.93,-144 487.03,-150.94"/>
|
||||
</g>
|
||||
<!-- metric_type -->
|
||||
<g id="node8" class="node">
|
||||
<title>metric_type</title>
|
||||
<polygon fill="#e1bee7" stroke="black" points="827.25,-19.75 827.25,-104.25 1006.25,-104.25 1006.25,-19.75 827.25,-19.75"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="861.88" y="-58.88" font-family="Helvetica,sans-Serif" font-size="10.00">MetricType</text>
|
||||
<polyline fill="none" stroke="black" points="896.5,-19.75 896.5,-104.25"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="904.5" y="-90.75" font-family="Helvetica,sans-Serif" font-size="10.00">CPU_PERCENT</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="904.5" y="-78" font-family="Helvetica,sans-Serif" font-size="10.00">MEMORY_PERCENT</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="904.5" y="-65.25" font-family="Helvetica,sans-Serif" font-size="10.00">DISK_PERCENT</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="904.5" y="-52.5" font-family="Helvetica,sans-Serif" font-size="10.00">NETWORK_*</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="904.5" y="-39.75" font-family="Helvetica,sans-Serif" font-size="10.00">LOAD_AVG_*</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="951.38" y="-27" font-family="Helvetica,sans-Serif" font-size="10.00">...</text>
|
||||
</g>
|
||||
<!-- metric_msg->metric_type -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>metric_msg->metric_type</title>
|
||||
<path fill="none" stroke="black" stroke-dasharray="1,5" d="M692.74,-59.81C730.57,-60.18 775.71,-60.63 815.45,-61.02"/>
|
||||
<polygon fill="black" stroke="black" points="815.23,-64.51 825.27,-61.11 815.3,-57.51 815.23,-64.51"/>
|
||||
</g>
|
||||
<!-- health_status -->
|
||||
<g id="node9" class="node">
|
||||
<title>health_status</title>
|
||||
<polygon fill="#ce93d8" stroke="black" points="842.25,-123.12 842.25,-194.88 991.25,-194.88 991.25,-123.12 842.25,-123.12"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="881.75" y="-155.88" font-family="Helvetica,sans-Serif" font-size="10.00">HealthStatus</text>
|
||||
<polyline fill="none" stroke="black" points="921.25,-123.12 921.25,-194.88"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="929.25" y="-181.38" font-family="Helvetica,sans-Serif" font-size="10.00">HEALTHY</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="929.25" y="-168.62" font-family="Helvetica,sans-Serif" font-size="10.00">WARNING</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="929.25" y="-155.88" font-family="Helvetica,sans-Serif" font-size="10.00">CRITICAL</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="929.25" y="-143.12" font-family="Helvetica,sans-Serif" font-size="10.00">UNKNOWN</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="929.25" y="-130.38" font-family="Helvetica,sans-Serif" font-size="10.00">OFFLINE</text>
|
||||
</g>
|
||||
<!-- machine_state->health_status -->
|
||||
<g id="edge6" class="edge">
|
||||
<title>machine_state->health_status</title>
|
||||
<path fill="none" stroke="black" stroke-dasharray="1,5" d="M719.09,-161.57C755.76,-161.09 796.1,-160.57 830.65,-160.11"/>
|
||||
<polygon fill="black" stroke="black" points="830.67,-163.61 840.62,-159.98 830.58,-156.61 830.67,-163.61"/>
|
||||
</g>
|
||||
<!-- control_svc -->
|
||||
<g id="node4" class="node">
|
||||
<title>control_svc</title>
|
||||
<polygon fill="#90caf9" stroke="black" points="31.38,-261 31.38,-297 375.38,-297 375.38,-261 31.38,-261"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="75" y="-276" font-family="Helvetica,sans-Serif" font-size="10.00">ControlService</text>
|
||||
<polyline fill="none" stroke="black" points="118.62,-261.25 118.62,-297"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="126.62" y="-276" font-family="Helvetica,sans-Serif" font-size="10.00">+ Control(stream Command) → stream Response</text>
|
||||
</g>
|
||||
<!-- commands -->
|
||||
<g id="node5" class="node">
|
||||
<title>commands</title>
|
||||
<polygon fill="#64b5f6" stroke="black" points="426.75,-255.88 426.75,-302.12 791.25,-302.12 791.25,-255.88 426.75,-255.88"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="477.5" y="-275.88" font-family="Helvetica,sans-Serif" font-size="10.00">ControlCommand</text>
|
||||
<polyline fill="none" stroke="black" points="528.25,-255.88 528.25,-302.12"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="536.25" y="-275.88" font-family="Helvetica,sans-Serif" font-size="10.00">command_id: string</text>
|
||||
<polyline fill="none" stroke="black" points="641,-255.88 641,-302.12"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="649" y="-288.62" font-family="Helvetica,sans-Serif" font-size="10.00">UpdateIntervalCommand</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="649" y="-275.88" font-family="Helvetica,sans-Serif" font-size="10.00">RestartCollectionCommand</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="649" y="-263.12" font-family="Helvetica,sans-Serif" font-size="10.00">ShutdownCommand</text>
|
||||
</g>
|
||||
<!-- control_svc->commands -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>control_svc->commands</title>
|
||||
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M375.84,-279C388.79,-279 401.92,-279 414.99,-279"/>
|
||||
<polygon fill="black" stroke="black" points="414.95,-282.5 424.95,-279 414.95,-275.5 414.95,-282.5"/>
|
||||
</g>
|
||||
<!-- config_svc -->
|
||||
<g id="node6" class="node">
|
||||
<title>config_svc</title>
|
||||
<polygon fill="#ffe0b2" stroke="black" points="16,-364 16,-400 390.75,-400 390.75,-364 16,-364"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="57.38" y="-379.12" font-family="Helvetica,sans-Serif" font-size="10.00">ConfigService</text>
|
||||
<polyline fill="none" stroke="black" points="98.75,-364.5 98.75,-400"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="106.75" y="-385.5" font-family="Helvetica,sans-Serif" font-size="10.00">+ GetConfig(ConfigRequest) → CollectorConfig</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="106.75" y="-372.75" font-family="Helvetica,sans-Serif" font-size="10.00">+ WatchConfig(ConfigRequest) → stream CollectorConfig</text>
|
||||
</g>
|
||||
<!-- collector_config -->
|
||||
<g id="node7" class="node">
|
||||
<title>collector_config</title>
|
||||
<polygon fill="#ffcc80" stroke="black" points="473,-352.5 473,-411.5 745,-411.5 745,-352.5 473,-352.5"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="518.12" y="-378.88" font-family="Helvetica,sans-Serif" font-size="10.00">CollectorConfig</text>
|
||||
<polyline fill="none" stroke="black" points="563.25,-352.5 563.25,-411.5"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="571.25" y="-398" font-family="Helvetica,sans-Serif" font-size="10.00">collection_interval_seconds: int32</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="571.25" y="-385.25" font-family="Helvetica,sans-Serif" font-size="10.00">enabled_metrics: MetricType[]</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="571.25" y="-372.5" font-family="Helvetica,sans-Serif" font-size="10.00">labels: map</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="571.25" y="-359.75" font-family="Helvetica,sans-Serif" font-size="10.00">thresholds: ThresholdConfig[]</text>
|
||||
</g>
|
||||
<!-- config_svc->collector_config -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>config_svc->collector_config</title>
|
||||
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M391.12,-382C414.61,-382 438.36,-382 461.11,-382"/>
|
||||
<polygon fill="black" stroke="black" points="461.03,-385.5 471.03,-382 461.03,-378.5 461.03,-385.5"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 13 KiB |
130
atlas/books/sysmonstm/architecture/graph.html
Normal file
@@ -0,0 +1,130 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Graph Viewer - System Monitor</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
<body class="graph-viewer">
|
||||
<header class="graph-header">
|
||||
<a href="../index.html" class="back-link">← Index</a>
|
||||
<div class="nav-controls">
|
||||
<button
|
||||
onclick="navigate(-1)"
|
||||
id="btn-prev"
|
||||
title="Previous (←)"
|
||||
>
|
||||
◀
|
||||
</button>
|
||||
<span id="nav-position">1 / 4</span>
|
||||
<button onclick="navigate(1)" id="btn-next" title="Next (→)">
|
||||
▶
|
||||
</button>
|
||||
</div>
|
||||
<h1 id="graph-title">Loading...</h1>
|
||||
<div class="graph-controls">
|
||||
<button onclick="setMode('fit')">Fit</button>
|
||||
<button onclick="setMode('fit-width')">Width</button>
|
||||
<button onclick="setMode('fit-height')">Height</button>
|
||||
<button onclick="setMode('actual-size')">100%</button>
|
||||
<button onclick="downloadSvg()">↓ SVG</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="graph-container" id="graph-container">
|
||||
<img id="graph-img" src="" alt="Graph" />
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const graphOrder = [
|
||||
"01-system-overview",
|
||||
"02-data-flow",
|
||||
"03-deployment",
|
||||
"04-grpc-services",
|
||||
];
|
||||
|
||||
const graphs = {
|
||||
"01-system-overview": {
|
||||
title: "System Overview",
|
||||
file: "01-system-overview.svg",
|
||||
},
|
||||
"02-data-flow": {
|
||||
title: "Data Flow Pipeline",
|
||||
file: "02-data-flow.svg",
|
||||
},
|
||||
"03-deployment": {
|
||||
title: "Deployment Architecture",
|
||||
file: "03-deployment.svg",
|
||||
},
|
||||
"04-grpc-services": {
|
||||
title: "gRPC Service Definitions",
|
||||
file: "04-grpc-services.svg",
|
||||
},
|
||||
};
|
||||
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
let graphKey = params.get("g") || "01-system-overview";
|
||||
let currentIndex = graphOrder.indexOf(graphKey);
|
||||
if (currentIndex === -1) currentIndex = 0;
|
||||
|
||||
function loadGraph(key) {
|
||||
const graph = graphs[key];
|
||||
document.getElementById("graph-title").textContent =
|
||||
graph.title;
|
||||
document.getElementById("graph-img").src = graph.file;
|
||||
document.title = graph.title + " - System Monitor";
|
||||
history.replaceState(null, "", "?g=" + key);
|
||||
graphKey = key;
|
||||
updateNavHints();
|
||||
}
|
||||
|
||||
function updateNavHints() {
|
||||
const idx = graphOrder.indexOf(graphKey);
|
||||
const prevBtn = document.getElementById("btn-prev");
|
||||
const nextBtn = document.getElementById("btn-next");
|
||||
prevBtn.disabled = idx === 0;
|
||||
nextBtn.disabled = idx === graphOrder.length - 1;
|
||||
document.getElementById("nav-position").textContent =
|
||||
idx + 1 + " / " + graphOrder.length;
|
||||
}
|
||||
|
||||
function navigate(direction) {
|
||||
const idx = graphOrder.indexOf(graphKey);
|
||||
const newIdx = idx + direction;
|
||||
if (newIdx >= 0 && newIdx < graphOrder.length) {
|
||||
currentIndex = newIdx;
|
||||
loadGraph(graphOrder[newIdx]);
|
||||
}
|
||||
}
|
||||
|
||||
function setMode(mode) {
|
||||
const container = document.getElementById("graph-container");
|
||||
container.className = "graph-container " + mode;
|
||||
}
|
||||
|
||||
function downloadSvg() {
|
||||
const graph = graphs[graphKey];
|
||||
const link = document.createElement("a");
|
||||
link.href = graph.file;
|
||||
link.download = graph.file;
|
||||
link.click();
|
||||
}
|
||||
|
||||
// Keyboard navigation
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if (e.key === "ArrowLeft") {
|
||||
navigate(-1);
|
||||
} else if (e.key === "ArrowRight") {
|
||||
navigate(1);
|
||||
} else if (e.key === "Escape") {
|
||||
window.location.href = "../index.html";
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize
|
||||
loadGraph(graphOrder[currentIndex]);
|
||||
setMode("fit");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
262
atlas/books/sysmonstm/architecture/index.html
Normal file
@@ -0,0 +1,262 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta http-equiv="refresh" content="0; url=../index.html" />
|
||||
<title>System Monitor - Redirecting...</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>System Monitoring Platform</h1>
|
||||
<p class="subtitle">Architecture & Design Documentation</p>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<section class="graph-section" id="overview">
|
||||
<div class="graph-header-row">
|
||||
<h2>System Overview</h2>
|
||||
<a href="graph.html?g=01-system-overview" class="view-btn"
|
||||
>View Full</a
|
||||
>
|
||||
</div>
|
||||
<a href="graph.html?g=01-system-overview" class="graph-preview">
|
||||
<img src="01-system-overview.svg" alt="System Overview" />
|
||||
</a>
|
||||
<div class="graph-details">
|
||||
<p>
|
||||
High-level architecture showing all services, data
|
||||
stores, and communication patterns.
|
||||
</p>
|
||||
<h4>Key Components</h4>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>Collector</strong>: Runs on each monitored
|
||||
machine, streams metrics via gRPC
|
||||
</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>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="graph-section" id="data-flow">
|
||||
<div class="graph-header-row">
|
||||
<h2>Data Flow Pipeline</h2>
|
||||
<a href="graph.html?g=02-data-flow" class="view-btn"
|
||||
>View Full</a
|
||||
>
|
||||
</div>
|
||||
<a href="graph.html?g=02-data-flow" class="graph-preview">
|
||||
<img src="02-data-flow.svg" alt="Data Flow" />
|
||||
</a>
|
||||
<div class="graph-details">
|
||||
<p>
|
||||
How metrics flow from collection through storage with
|
||||
different retention tiers.
|
||||
</p>
|
||||
<h4>Storage Tiers</h4>
|
||||
<table class="details-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Tier</th>
|
||||
<th>Resolution</th>
|
||||
<th>Retention</th>
|
||||
<th>Use Case</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Hot (Redis)</td>
|
||||
<td>5s</td>
|
||||
<td>5 min</td>
|
||||
<td>Current state, live dashboard</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Raw (TimescaleDB)</td>
|
||||
<td>5s</td>
|
||||
<td>24h</td>
|
||||
<td>Recent detailed analysis</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1-min Aggregates</td>
|
||||
<td>1m</td>
|
||||
<td>7d</td>
|
||||
<td>Week view, trends</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1-hour Aggregates</td>
|
||||
<td>1h</td>
|
||||
<td>90d</td>
|
||||
<td>Long-term analysis</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="graph-section" id="deployment">
|
||||
<div class="graph-header-row">
|
||||
<h2>Deployment Architecture</h2>
|
||||
<a href="graph.html?g=03-deployment" class="view-btn"
|
||||
>View Full</a
|
||||
>
|
||||
</div>
|
||||
<a href="graph.html?g=03-deployment" class="graph-preview">
|
||||
<img src="03-deployment.svg" alt="Deployment" />
|
||||
</a>
|
||||
<div class="graph-details">
|
||||
<p>
|
||||
Deployment options from local development to AWS
|
||||
production.
|
||||
</p>
|
||||
<h4>Environments</h4>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>Local Dev</strong>: Kind + Tilt for K8s, or
|
||||
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>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="graph-section" id="grpc">
|
||||
<div class="graph-header-row">
|
||||
<h2>gRPC Service Definitions</h2>
|
||||
<a href="graph.html?g=04-grpc-services" class="view-btn"
|
||||
>View Full</a
|
||||
>
|
||||
</div>
|
||||
<a href="graph.html?g=04-grpc-services" class="graph-preview">
|
||||
<img src="04-grpc-services.svg" alt="gRPC Services" />
|
||||
</a>
|
||||
<div class="graph-details">
|
||||
<p>Protocol Buffer service and message definitions.</p>
|
||||
<h4>Services</h4>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>MetricsService</strong>: Client-side
|
||||
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>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="findings-section">
|
||||
<h2>Interview Talking Points</h2>
|
||||
<div class="findings-grid">
|
||||
<article class="finding-card">
|
||||
<h3>Domain Mapping</h3>
|
||||
<ul>
|
||||
<li>Machine = Payment Processor</li>
|
||||
<li>Metrics Stream = Transaction Stream</li>
|
||||
<li>Thresholds = Fraud Detection</li>
|
||||
<li>Aggregator = Payment Hub</li>
|
||||
</ul>
|
||||
</article>
|
||||
<article class="finding-card">
|
||||
<h3>gRPC Patterns</h3>
|
||||
<ul>
|
||||
<li>Client streaming (metrics)</li>
|
||||
<li>Server streaming (config)</li>
|
||||
<li>Bidirectional (control)</li>
|
||||
<li>Health checking</li>
|
||||
</ul>
|
||||
</article>
|
||||
<article class="finding-card">
|
||||
<h3>Event-Driven</h3>
|
||||
<ul>
|
||||
<li>Redis Pub/Sub (current)</li>
|
||||
<li>Abstraction for Kafka switch</li>
|
||||
<li>Decoupled alert processing</li>
|
||||
<li>Real-time WebSocket push</li>
|
||||
</ul>
|
||||
</article>
|
||||
<article class="finding-card">
|
||||
<h3>Resilience</h3>
|
||||
<ul>
|
||||
<li>Collectors are independent</li>
|
||||
<li>Graceful degradation</li>
|
||||
<li>Retry with backoff</li>
|
||||
<li>Health checks everywhere</li>
|
||||
</ul>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="tech-section">
|
||||
<h2>Technology Stack</h2>
|
||||
<div class="tech-grid">
|
||||
<div class="tech-column">
|
||||
<h3>Core</h3>
|
||||
<ul>
|
||||
<li>Python 3.11+</li>
|
||||
<li>FastAPI</li>
|
||||
<li>gRPC / protobuf</li>
|
||||
<li>asyncio</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tech-column">
|
||||
<h3>Data</h3>
|
||||
<ul>
|
||||
<li>TimescaleDB</li>
|
||||
<li>Redis</li>
|
||||
<li>Redis Pub/Sub</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tech-column">
|
||||
<h3>Infrastructure</h3>
|
||||
<ul>
|
||||
<li>Docker</li>
|
||||
<li>Kubernetes</li>
|
||||
<li>Kind + Tilt</li>
|
||||
<li>Terraform</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tech-column">
|
||||
<h3>CI/CD</h3>
|
||||
<ul>
|
||||
<li>Woodpecker CI</li>
|
||||
<li>Kustomize</li>
|
||||
<li>Container Registry</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>System Monitoring Platform - Architecture Documentation</p>
|
||||
<p class="date">
|
||||
Generated: <time datetime="2025-12-29">December 2025</time>
|
||||
</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
343
atlas/books/sysmonstm/architecture/styles.css
Normal file
@@ -0,0 +1,343 @@
|
||||
:root {
|
||||
--bg-primary: #1a1a2e;
|
||||
--bg-secondary: #16213e;
|
||||
--bg-card: #0f3460;
|
||||
--text-primary: #eee;
|
||||
--text-secondary: #a0a0a0;
|
||||
--accent: #e94560;
|
||||
--accent-secondary: #533483;
|
||||
--border: #2a2a4a;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
header {
|
||||
background: linear-gradient(135deg, var(--bg-secondary), var(--accent-secondary));
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
border-bottom: 2px solid var(--accent);
|
||||
}
|
||||
|
||||
header h1 {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
header .subtitle {
|
||||
color: var(--text-secondary);
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
main {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
/* Graph sections */
|
||||
.graph-section {
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.graph-header-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.graph-header-row h2 {
|
||||
font-size: 1.25rem;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.view-btn {
|
||||
background: var(--accent);
|
||||
color: white;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 4px;
|
||||
text-decoration: none;
|
||||
font-size: 0.875rem;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.view-btn:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.graph-preview {
|
||||
display: block;
|
||||
background: white;
|
||||
border-radius: 4px;
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
overflow: auto;
|
||||
max-height: 400px;
|
||||
}
|
||||
|
||||
.graph-preview img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.graph-details {
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.graph-details h4 {
|
||||
color: var(--text-primary);
|
||||
margin: 1rem 0 0.5rem;
|
||||
}
|
||||
|
||||
.graph-details ul {
|
||||
margin-left: 1.5rem;
|
||||
}
|
||||
|
||||
.graph-details li {
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
/* Tech section */
|
||||
.tech-section {
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.tech-section h2 {
|
||||
color: var(--accent);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.tech-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.tech-column h3 {
|
||||
color: var(--text-primary);
|
||||
font-size: 1rem;
|
||||
margin-bottom: 0.75rem;
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.tech-column ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.tech-column li {
|
||||
padding: 0.25rem 0;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* Findings */
|
||||
.findings-section {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.findings-section h2 {
|
||||
color: var(--accent);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.findings-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.finding-card {
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 8px;
|
||||
padding: 1.25rem;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.finding-card h3 {
|
||||
color: var(--accent);
|
||||
font-size: 1rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.finding-card ul {
|
||||
margin-left: 1rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.finding-card code {
|
||||
background: var(--bg-primary);
|
||||
padding: 0.125rem 0.375rem;
|
||||
border-radius: 3px;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
footer {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
color: var(--text-secondary);
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
|
||||
footer .date {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
/* Graph viewer page */
|
||||
body.graph-viewer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.graph-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
padding: 0.75rem 1rem;
|
||||
background: var(--bg-secondary);
|
||||
border-bottom: 1px solid var(--border);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.back-link {
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.nav-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.nav-controls button {
|
||||
background: var(--bg-card);
|
||||
color: var(--text-primary);
|
||||
border: 1px solid var(--border);
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.nav-controls button:disabled {
|
||||
opacity: 0.3;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
#nav-position {
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.graph-header h1 {
|
||||
flex: 1;
|
||||
font-size: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.graph-controls {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.graph-controls button {
|
||||
background: var(--bg-card);
|
||||
color: var(--text-primary);
|
||||
border: 1px solid var(--border);
|
||||
padding: 0.375rem 0.75rem;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.graph-controls button:hover {
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
.graph-container {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
background: white;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.graph-container.fit img {
|
||||
max-width: 100%;
|
||||
max-height: calc(100vh - 60px);
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.graph-container.fit-width img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.graph-container.fit-height img {
|
||||
height: calc(100vh - 60px);
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.graph-container.actual-size img {
|
||||
/* No constraints */
|
||||
}
|
||||
|
||||
/* Tables */
|
||||
.details-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 1rem 0;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.details-table th,
|
||||
.details-table td {
|
||||
padding: 0.5rem;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.details-table th {
|
||||
color: var(--text-primary);
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
|
||||
.details-table td {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.details-table code {
|
||||
background: var(--bg-primary);
|
||||
padding: 0.125rem 0.375rem;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.note {
|
||||
font-style: italic;
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-secondary);
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
@@ -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 |
83
atlas/books/sysmonstm/explainer/images/02-grpc-streaming.svg
Normal 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 |
83
atlas/books/sysmonstm/explainer/images/03-storage-tiers.svg
Normal 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 |
84
atlas/books/sysmonstm/explainer/images/04-event-driven.svg
Normal 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 |
80
atlas/books/sysmonstm/explainer/images/05-domain-mapping.svg
Normal 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 |
@@ -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 |
282
atlas/books/sysmonstm/explainer/other-applications.md
Normal file
@@ -0,0 +1,282 @@
|
||||
# Same Patterns, Different Domains
|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
## Payment Processing Systems
|
||||
|
||||
The sysmonstm architecture was intentionally designed to map to payment processing. Here's how each component translates.
|
||||
|
||||
### Domain Mapping
|
||||
|
||||
| sysmonstm | Payment System |
|
||||
|-----------|----------------|
|
||||
| Machine | Payment Processor (Stripe, PayPal, bank API) |
|
||||
| Metrics Stream | Transaction Stream |
|
||||
| Aggregator | Payment Hub |
|
||||
| Alert Thresholds | Fraud Detection Rules |
|
||||
| Alert Service | Risk Management |
|
||||
| Redis (current state) | Transaction Cache |
|
||||
| TimescaleDB (history) | Transaction Ledger |
|
||||
| Event Stream | Audit Trail |
|
||||
|
||||
### How It Would Work
|
||||
|
||||
**Collectors become processor adapters.** Instead of collecting CPU and memory via psutil, each adapter connects to a payment processor's API or webhook endpoint:
|
||||
|
||||
```python
|
||||
# Conceptual - not actual code
|
||||
class StripeAdapter:
|
||||
async def stream_transactions(self):
|
||||
async for event in stripe.webhook_events():
|
||||
yield Transaction(
|
||||
processor="stripe",
|
||||
amount=event.amount,
|
||||
currency=event.currency,
|
||||
status=event.status,
|
||||
customer_id=event.customer,
|
||||
timestamp=event.created,
|
||||
)
|
||||
```
|
||||
|
||||
The gRPC streaming pattern remains identical. Each adapter streams transactions to a central aggregator.
|
||||
|
||||
**The aggregator normalizes data.** Stripe sends amounts in cents. PayPal sends them in dollars. Bank APIs use different currency codes. The aggregator normalizes everything to a consistent format before storage:
|
||||
|
||||
```python
|
||||
# In the aggregator's StreamTransactions handler
|
||||
async for tx in request_iterator:
|
||||
normalized = normalize_transaction(tx)
|
||||
await self.store(normalized)
|
||||
await self.publisher.publish("transactions.raw", normalized)
|
||||
```
|
||||
|
||||
This is the same pattern as `services/aggregator/main.py:47-95` - receive stream, batch, flush to storage, publish events.
|
||||
|
||||
**Alerts become fraud detection.** Instead of "CPU > 80%", rules look like:
|
||||
|
||||
- Transaction amount > $10,000 (large transaction)
|
||||
- More than 5 transactions from same card in 1 minute (velocity check)
|
||||
- Transaction from country different than cardholder's (geographic anomaly)
|
||||
|
||||
The `AlertEvaluator` pattern from `services/alerts/main.py:44-77` handles this:
|
||||
|
||||
```python
|
||||
class FraudEvaluator:
|
||||
RULES = [
|
||||
FraudRule("large_transaction", "amount", "gt", 10000, "review"),
|
||||
FraudRule("velocity", "transactions_per_minute", "gt", 5, "block"),
|
||||
]
|
||||
|
||||
def evaluate(self, transaction: dict) -> list[FraudAlert]:
|
||||
# Same operator-based evaluation as AlertEvaluator
|
||||
pass
|
||||
```
|
||||
|
||||
**The event stream becomes an audit trail.** Financial systems require complete audit logs. Every transaction, every state change, every decision must be recorded. The event abstraction from `shared/events/base.py` already provides this:
|
||||
|
||||
```python
|
||||
await self.publisher.publish(
|
||||
topic="transactions.processed",
|
||||
payload={
|
||||
"transaction_id": tx.id,
|
||||
"processor": tx.processor,
|
||||
"amount": tx.amount,
|
||||
"decision": "approved",
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
Subscribe to these events for compliance reporting, analytics, or real-time monitoring.
|
||||
|
||||
**Tiered storage handles transaction volumes.** Hot transactions (last hour) in Redis for quick lookups. Recent transactions (last month) in PostgreSQL for operational queries. Historical transactions archived to S3 for compliance retention. Same pattern as sysmonstm's Redis + TimescaleDB setup.
|
||||
|
||||
### What Changes
|
||||
|
||||
- **Authentication**: Payment APIs require OAuth, API keys, mTLS. The collector adapters need credential management.
|
||||
- **Idempotency**: Transactions must be processed exactly once. The aggregator needs deduplication.
|
||||
- **Compliance**: PCI-DSS requires encryption, access controls, audit logging. More infrastructure, same patterns.
|
||||
|
||||
### What Stays the Same
|
||||
|
||||
- gRPC streaming from multiple sources to central aggregator
|
||||
- Event-driven processing for decoupled services
|
||||
- Threshold-based alerting
|
||||
- Real-time dashboard via WebSocket
|
||||
- Tiered storage for different access patterns
|
||||
|
||||
## Deskmeter: A Workspace Timer Application
|
||||
|
||||
Deskmeter is a productivity tracking application that monitors desktop workspace switches and task changes. It runs on Linux, tracks time spent on different tasks, and displays the data through a web dashboard.
|
||||
|
||||
Current architecture:
|
||||
- **dmcore daemon**: Polls workspace state every 2 seconds using `wmctrl`
|
||||
- **MongoDB**: Stores workspace switches with timestamps and durations
|
||||
- **Flask web server**: Serves calendar views and task summaries
|
||||
- **GNOME extension**: Shows current task in the top panel
|
||||
|
||||
This works, but sysmonstm patterns could enhance it significantly.
|
||||
|
||||

|
||||
|
||||
### Current Deskmeter Implementation
|
||||
|
||||
The core daemon (`dmapp/dmcore/main.py`) polls in a loop:
|
||||
|
||||
```python
|
||||
while True:
|
||||
current_workspace = active_workspace() # Calls wmctrl
|
||||
current_task = state.retrieve("current").get("task")
|
||||
|
||||
# Track the switch
|
||||
last_switch_time = track_workspace_switch(
|
||||
current_workspace,
|
||||
current_task,
|
||||
last_switch_time
|
||||
)
|
||||
|
||||
time.sleep(2)
|
||||
```
|
||||
|
||||
The web server (`dmapp/dmweb/dm.py`) uses Flask with template rendering:
|
||||
|
||||
```python
|
||||
@dmbp.route("/calendar/<string:scope>")
|
||||
def calendar_view(scope="daily", year=None, month=None, day=None):
|
||||
blocks = get_task_blocks_calendar(start, end, task, ...)
|
||||
return render_template("calendar_view.html", blocks=blocks, ...)
|
||||
```
|
||||
|
||||
The dashboard refreshes via page reload or AJAX polling.
|
||||
|
||||
### How sysmonstm Patterns Would Improve It
|
||||
|
||||
**Replace polling with streaming.** Instead of the daemon polling every 2 seconds and the web dashboard polling for updates, use the same event-driven architecture as sysmonstm.
|
||||
|
||||
The daemon becomes an event publisher:
|
||||
|
||||
```python
|
||||
# Conceptual improvement
|
||||
class WorkspaceMonitor:
|
||||
async def run(self):
|
||||
publisher = get_publisher(source="workspace-monitor")
|
||||
await publisher.connect()
|
||||
|
||||
while self.running:
|
||||
workspace = await self.detect_workspace()
|
||||
task = await self.get_current_task()
|
||||
|
||||
if workspace != self.last_workspace or task != self.last_task:
|
||||
await publisher.publish(
|
||||
topic="workspace.switch",
|
||||
payload={
|
||||
"workspace": workspace,
|
||||
"task": task,
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
},
|
||||
)
|
||||
self.last_workspace = workspace
|
||||
self.last_task = task
|
||||
|
||||
await asyncio.sleep(2)
|
||||
```
|
||||
|
||||
The web server subscribes to events and pushes to browsers via WebSocket - exactly like `services/gateway/main.py:88-130`:
|
||||
|
||||
```python
|
||||
async def event_listener():
|
||||
async with get_subscriber(topics=["workspace.*"]) as subscriber:
|
||||
async for event in subscriber.consume():
|
||||
await manager.broadcast({
|
||||
"type": "workspace_switch",
|
||||
"data": event.payload,
|
||||
})
|
||||
```
|
||||
|
||||
The GNOME extension could subscribe directly instead of polling an HTTP endpoint.
|
||||
|
||||
**Add multi-machine support.** With sysmonstm's architecture, tracking multiple machines is trivial. Run the workspace monitor daemon on each machine. Each streams events to an aggregator. The dashboard shows all machines.
|
||||
|
||||
```python
|
||||
# Each machine's monitor includes machine_id
|
||||
await publisher.publish(
|
||||
topic="workspace.switch",
|
||||
payload={
|
||||
"machine_id": self.machine_id, # "workstation", "laptop", etc.
|
||||
"workspace": workspace,
|
||||
"task": task,
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
The dashboard groups by machine or shows a combined view. Same pattern as sysmonstm's multi-machine monitoring.
|
||||
|
||||
**Add focus alerts.** The alert service pattern from `services/alerts/main.py` applies directly:
|
||||
|
||||
```python
|
||||
# Focus time rules
|
||||
FocusRule("context_switching", "switches_per_hour", "gt", 10, "warning")
|
||||
FocusRule("long_idle", "idle_minutes", "gt", 30, "info")
|
||||
FocusRule("deep_work", "focus_minutes", "gt", 90, "success")
|
||||
```
|
||||
|
||||
When you switch tasks more than 10 times in an hour, get a notification. When you've been focused for 90 minutes, celebrate. The evaluator pattern handles both alerts and achievements.
|
||||
|
||||
**Improve time-series storage.** Deskmeter uses MongoDB for everything. With sysmonstm's tiered approach:
|
||||
|
||||
- **Redis**: Current task, current workspace, last 5 minutes of switches
|
||||
- **TimescaleDB**: Historical switches with automatic downsampling
|
||||
|
||||
Query "what was I doing at 3pm yesterday" hits warm storage. Query "how much time did I spend on project X this month" uses aggregated data. Same queries, faster execution.
|
||||
|
||||
### Implementation Path
|
||||
|
||||
1. **Add event publishing to dmcore.** Keep the polling loop but publish events instead of writing directly to MongoDB.
|
||||
|
||||
2. **Add WebSocket to dmweb.** Subscribe to events, push to connected browsers. The calendar view updates in real-time.
|
||||
|
||||
3. **Add Redis for current state.** Dashboard reads current task from Redis instead of querying MongoDB.
|
||||
|
||||
4. **Add focus alerts.** New service that subscribes to workspace events, evaluates rules, publishes alerts.
|
||||
|
||||
5. **Add multi-machine support.** Run dmcore on multiple machines. Aggregate events centrally.
|
||||
|
||||
Each step is independent. The system works after each one. Same phased approach as sysmonstm.
|
||||
|
||||
### Code Mapping
|
||||
|
||||
| sysmonstm Component | Deskmeter Equivalent |
|
||||
|---------------------|---------------------|
|
||||
| `services/collector/` | `dmapp/dmcore/main.py` - workspace monitoring |
|
||||
| `services/aggregator/` | Event aggregation (new) |
|
||||
| `services/gateway/` | `dmapp/dmweb/dm.py` + WebSocket (enhanced) |
|
||||
| `services/alerts/` | Focus alerts service (new) |
|
||||
| `proto/metrics.proto` | Workspace event schema |
|
||||
| `shared/events/` | Same - reusable |
|
||||
|
||||
The event abstraction from sysmonstm (`shared/events/`) works directly. The configuration pattern from `shared/config.py` works directly. The structured logging from `shared/logging.py` works directly.
|
||||
|
||||
## The Common Thread
|
||||
|
||||
Both payment processing and productivity tracking share the same fundamental pattern:
|
||||
|
||||
1. **Multiple data sources** streaming to a central point
|
||||
2. **Normalization** of different formats into consistent schema
|
||||
3. **Real-time processing** for dashboards and alerts
|
||||
4. **Historical storage** for analysis and compliance
|
||||
5. **Event-driven decoupling** for extensibility
|
||||
|
||||
sysmonstm demonstrates these patterns with system metrics. The patterns transfer to any domain with similar characteristics:
|
||||
|
||||
- IoT sensor networks (temperature, humidity, motion)
|
||||
- Log aggregation (application logs from multiple services)
|
||||
- Social media analytics (tweets, posts, mentions)
|
||||
- Trading systems (market data from multiple exchanges)
|
||||
- Fleet management (GPS, fuel, diagnostics from vehicles)
|
||||
|
||||
The specific metrics change. The thresholds change. The domain vocabulary changes. The architecture stays the same.
|
||||
|
||||
Build it once for metrics. Apply it anywhere.
|
||||
@@ -0,0 +1,421 @@
|
||||
# Building sysmonstm: From Idea to Working System
|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||
The obvious solution is a monitoring dashboard. Something that shows all machines in one place, updates in real-time, and alerts before things break.
|
||||
|
||||
But the real motivation was an interview. The job description mentioned gRPC, streaming patterns, event-driven architecture. Building a monitoring system would demonstrate all of these while solving an actual problem.
|
||||
|
||||
## Architecture Decisions
|
||||
|
||||
### Why gRPC Instead of REST
|
||||
|
||||
REST would work fine. Poll each machine every few seconds, aggregate the results. Simple.
|
||||
|
||||
But gRPC offers streaming. Instead of the aggregator asking each machine "what are your metrics right now?", each machine opens a persistent connection and continuously pushes metrics. This is more efficient (one connection instead of repeated requests) and lower latency (metrics arrive as soon as they're collected).
|
||||
|
||||
The proto definition in `proto/metrics.proto` defines this as client-side streaming:
|
||||
|
||||
```protobuf
|
||||
service MetricsService {
|
||||
// Client-side streaming: collector streams metrics to aggregator
|
||||
rpc StreamMetrics(stream Metric) returns (StreamAck) {}
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
### Why This Storage Tier Approach
|
||||
|
||||
Metrics have different access patterns at different ages:
|
||||
|
||||
- **Right now**: The dashboard needs current CPU/memory/disk for all machines. Access pattern: read all, very frequently.
|
||||
- **Last hour**: Graphs showing recent trends. Access pattern: read range, somewhat frequently.
|
||||
- **Last week**: Investigating what happened yesterday. Access pattern: read range, occasionally.
|
||||
- **Last month**: Capacity planning. Access pattern: aggregated queries, rarely.
|
||||
|
||||
Storing everything in one place forces a choice between fast reads (keep it all in memory) and storage efficiency (keep it on disk). The solution is tiered storage:
|
||||
|
||||
- **Redis** (`services/aggregator/storage.py`): Current state only. Each machine's latest metrics, with 5-minute TTL. Dashboard reads hit Redis.
|
||||
- **TimescaleDB** (`scripts/init-db.sql`): Historical data. Raw metrics at 5-second resolution for 24 hours, then automatically downsampled to 1-minute and 1-hour aggregates with longer retention.
|
||||
|
||||
The aggregator writes to both on every batch. Redis for live dashboard. TimescaleDB for history.
|
||||
|
||||

|
||||
|
||||
### Why Event-Driven for Alerts
|
||||
|
||||
The alerts service needs to evaluate every metric against threshold rules. Two options:
|
||||
|
||||
1. **Direct call**: Aggregator calls alerts service for each metric batch.
|
||||
2. **Event stream**: Aggregator publishes events. Alerts service subscribes.
|
||||
|
||||
Option 2 decouples them. The aggregator doesn't know or care if the alerts service is running. It publishes events regardless. The alerts service can be restarted, scaled, or replaced without touching the aggregator.
|
||||
|
||||
The event abstraction in `shared/events/base.py` defines the interface:
|
||||
|
||||
```python
|
||||
class EventPublisher(ABC):
|
||||
@abstractmethod
|
||||
async def publish(self, topic: str, payload: dict[str, Any], **kwargs) -> str:
|
||||
pass
|
||||
|
||||
class EventSubscriber(ABC):
|
||||
@abstractmethod
|
||||
async def consume(self) -> AsyncIterator[Event]:
|
||||
pass
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
## Phase 1: MVP - Getting Streaming to Work
|
||||
|
||||
The goal was simple: run a collector, see metrics appear in the aggregator's logs.
|
||||
|
||||
### The Collector
|
||||
|
||||
`services/collector/main.py` is a gRPC client. The core is an async generator that yields metrics forever:
|
||||
|
||||
```python
|
||||
async def _metric_generator(self):
|
||||
"""Async generator that yields metrics at the configured interval."""
|
||||
while self.running:
|
||||
batch = self.collector.collect()
|
||||
protos = self._batch_to_proto(batch)
|
||||
|
||||
for proto in protos:
|
||||
yield proto
|
||||
|
||||
await asyncio.sleep(self.config.collection_interval)
|
||||
```
|
||||
|
||||
This generator is passed directly to the gRPC stub:
|
||||
|
||||
```python
|
||||
response = await self.stub.StreamMetrics(self._metric_generator())
|
||||
```
|
||||
|
||||
The gRPC library handles the streaming. Each `yield` sends a message. The connection stays open until the generator stops or the network fails.
|
||||
|
||||
The actual metric collection happens in `services/collector/metrics.py` using `psutil`:
|
||||
|
||||
```python
|
||||
def _collect_cpu(self) -> list[MetricValue]:
|
||||
metrics = []
|
||||
cpu_percent = psutil.cpu_percent(interval=None)
|
||||
metrics.append(MetricValue("CPU_PERCENT", cpu_percent))
|
||||
|
||||
per_core = psutil.cpu_percent(interval=None, percpu=True)
|
||||
for i, pct in enumerate(per_core):
|
||||
metrics.append(MetricValue(
|
||||
"CPU_PERCENT_PER_CORE",
|
||||
pct,
|
||||
{"core": str(i)}
|
||||
))
|
||||
return metrics
|
||||
```
|
||||
|
||||
### The Aggregator
|
||||
|
||||
`services/aggregator/main.py` is a gRPC server. The `StreamMetrics` method receives the stream:
|
||||
|
||||
```python
|
||||
async def StreamMetrics(self, request_iterator, context):
|
||||
metrics_received = 0
|
||||
current_batch: list[tuple[str, float, dict]] = []
|
||||
|
||||
async for metric in request_iterator:
|
||||
metrics_received += 1
|
||||
|
||||
metric_type = metrics_pb2.MetricType.Name(metric.type)
|
||||
current_batch.append((metric_type, metric.value, dict(metric.labels)))
|
||||
|
||||
if len(current_batch) >= 20:
|
||||
await self._flush_batch(...)
|
||||
current_batch = []
|
||||
```
|
||||
|
||||
The `request_iterator` is an async iterator over incoming metrics. The `async for` loop processes them as they arrive. Batching (flush every 20 metrics) reduces storage writes.
|
||||
|
||||
### Retry Logic
|
||||
|
||||
Networks fail. The collector needs to reconnect. The pattern is exponential backoff:
|
||||
|
||||
```python
|
||||
retry_count = 0
|
||||
max_retries = 10
|
||||
base_delay = 1.0
|
||||
|
||||
while self.running:
|
||||
try:
|
||||
await self.stub.StreamMetrics(self._metric_generator())
|
||||
retry_count = 0 # Success - reset counter
|
||||
except grpc.aio.AioRpcError as e:
|
||||
retry_count += 1
|
||||
delay = min(base_delay * (2**retry_count), 60.0) # Cap at 60 seconds
|
||||
await asyncio.sleep(delay)
|
||||
await self.disconnect()
|
||||
await self.connect()
|
||||
```
|
||||
|
||||
First failure waits 2 seconds. Second waits 4. Third waits 8. Capped at 60 seconds. After 10 failures, give up.
|
||||
|
||||
## Phase 2: Dashboard - Making It Visible
|
||||
|
||||
Metrics in logs are useless. A dashboard makes them useful.
|
||||
|
||||
### The Gateway
|
||||
|
||||
`services/gateway/main.py` is a FastAPI application serving two purposes:
|
||||
|
||||
1. **REST API**: Query current and historical metrics
|
||||
2. **WebSocket**: Push real-time updates to browsers
|
||||
|
||||
The WebSocket connection manager (`services/gateway/main.py:40-67`) tracks active connections:
|
||||
|
||||
```python
|
||||
class ConnectionManager:
|
||||
def __init__(self):
|
||||
self.active_connections: list[WebSocket] = []
|
||||
|
||||
async def broadcast(self, message: dict) -> None:
|
||||
data = json.dumps(message)
|
||||
for connection in self.active_connections:
|
||||
await connection.send_text(data)
|
||||
```
|
||||
|
||||
### Event to WebSocket Bridge
|
||||
|
||||
The gateway subscribes to the same event stream as alerts. When a metric event arrives, it broadcasts to all connected browsers:
|
||||
|
||||
```python
|
||||
async def event_listener():
|
||||
async with get_subscriber(topics=["metrics.raw", "alerts.*"]) as subscriber:
|
||||
async for event in subscriber.consume():
|
||||
await manager.broadcast({
|
||||
"type": "metrics",
|
||||
"data": event.payload,
|
||||
"timestamp": event.timestamp.isoformat(),
|
||||
})
|
||||
```
|
||||
|
||||
This runs as a background task, started in the FastAPI lifespan handler (`services/gateway/main.py:145-175`).
|
||||
|
||||
### Handling Partial Batches
|
||||
|
||||
The aggregator batches metrics (flush every 20). This means a single collection cycle might arrive as multiple events. The dashboard needs complete machine state, not partial updates.
|
||||
|
||||
Solution: merge incoming metrics into a cache (`services/gateway/main.py:108-120`):
|
||||
|
||||
```python
|
||||
machine_metrics_cache: dict[str, dict] = {}
|
||||
|
||||
# In event_listener:
|
||||
machine_id = event.payload.get("machine_id", "")
|
||||
incoming_metrics = event.payload.get("metrics", {})
|
||||
|
||||
if machine_id not in machine_metrics_cache:
|
||||
machine_metrics_cache[machine_id] = {}
|
||||
machine_metrics_cache[machine_id].update(incoming_metrics)
|
||||
```
|
||||
|
||||
New metrics merge with existing. The broadcast includes the full merged state.
|
||||
|
||||
## Phase 3: Alerts - Adding Intelligence
|
||||
|
||||
The alerts service subscribes to metric events and evaluates them against rules.
|
||||
|
||||
### Rule Evaluation
|
||||
|
||||
`services/alerts/main.py` defines an `AlertEvaluator` class:
|
||||
|
||||
```python
|
||||
class AlertEvaluator:
|
||||
OPERATORS = {
|
||||
"gt": lambda v, t: v > t,
|
||||
"lt": lambda v, t: v < t,
|
||||
"gte": lambda v, t: v >= t,
|
||||
"lte": lambda v, t: v <= t,
|
||||
"eq": lambda v, t: v == t,
|
||||
}
|
||||
|
||||
def evaluate(self, machine_id: str, metrics: dict[str, float]) -> list[Alert]:
|
||||
new_alerts = []
|
||||
for metric_type, value in metrics.items():
|
||||
rule = self.rules.get(metric_type)
|
||||
if not rule:
|
||||
continue
|
||||
|
||||
op_func = self.OPERATORS.get(rule.operator)
|
||||
if op_func(value, rule.threshold):
|
||||
# Threshold exceeded
|
||||
new_alerts.append(Alert(...))
|
||||
return new_alerts
|
||||
```
|
||||
|
||||
### Avoiding Duplicate Alerts
|
||||
|
||||
If CPU stays above 80% for an hour, we want one alert, not 720 (one per 5-second check).
|
||||
|
||||
The evaluator tracks active alerts:
|
||||
|
||||
```python
|
||||
self.active_alerts: dict[str, Alert] = {} # key: f"{machine_id}:{rule_name}"
|
||||
|
||||
# In evaluate():
|
||||
alert_key = f"{machine_id}:{rule.name}"
|
||||
if op_func(value, rule.threshold):
|
||||
if alert_key not in self.active_alerts:
|
||||
# New alert - trigger it
|
||||
self.active_alerts[alert_key] = alert
|
||||
new_alerts.append(alert)
|
||||
# Otherwise already active - ignore
|
||||
else:
|
||||
# Threshold no longer exceeded - resolve
|
||||
if alert_key in self.active_alerts:
|
||||
del self.active_alerts[alert_key]
|
||||
```
|
||||
|
||||
New alert only triggers if not already in `active_alerts`. When the metric drops below threshold, the alert is removed and can trigger again later.
|
||||
|
||||
## Phase 4: Polish - Production Patterns
|
||||
|
||||
### Structured Logging
|
||||
|
||||
Every service uses `shared/logging.py` for structured JSON logging:
|
||||
|
||||
```python
|
||||
logger.info(
|
||||
"stream_completed",
|
||||
machine_id=current_machine,
|
||||
metrics_received=metrics_received,
|
||||
)
|
||||
```
|
||||
|
||||
Output:
|
||||
```json
|
||||
{"event": "stream_completed", "machine_id": "workstation", "metrics_received": 1500, "timestamp": "..."}
|
||||
```
|
||||
|
||||
This is searchable. "Show me all logs where metrics_received > 1000" is a simple query.
|
||||
|
||||
### Health Checks
|
||||
|
||||
Every service has health endpoints. The aggregator uses gRPC health checking (`services/aggregator/main.py:236-240`):
|
||||
|
||||
```python
|
||||
health_servicer = health.HealthServicer()
|
||||
health_servicer.set("", health_pb2.HealthCheckResponse.SERVING)
|
||||
health_servicer.set("MetricsService", health_pb2.HealthCheckResponse.SERVING)
|
||||
health_pb2_grpc.add_HealthServicer_to_server(health_servicer, self.server)
|
||||
```
|
||||
|
||||
The gateway has HTTP health endpoints (`services/gateway/main.py:197-216`):
|
||||
|
||||
```python
|
||||
@app.get("/ready")
|
||||
async def readiness_check():
|
||||
checks = {"gateway": "ok"}
|
||||
|
||||
try:
|
||||
await grpc_stub.GetAllStates(metrics_pb2.Empty(), timeout=2.0)
|
||||
checks["aggregator"] = "ok"
|
||||
except Exception as e:
|
||||
checks["aggregator"] = f"error: {str(e)}"
|
||||
|
||||
return {"status": "ready", "checks": checks}
|
||||
```
|
||||
|
||||
### Graceful Degradation
|
||||
|
||||
The aggregator continues streaming even if storage fails (`services/aggregator/main.py:137-152`):
|
||||
|
||||
```python
|
||||
try:
|
||||
await self.redis.update_machine_state(...)
|
||||
except Exception as e:
|
||||
self.logger.warning("redis_update_failed", error=str(e))
|
||||
# Don't re-raise - continue processing
|
||||
|
||||
try:
|
||||
await self.timescale.insert_metrics(...)
|
||||
except Exception as e:
|
||||
self.logger.warning("timescale_insert_failed", error=str(e))
|
||||
# Don't re-raise - continue processing
|
||||
```
|
||||
|
||||
Redis down? Metrics still flow to TimescaleDB. TimescaleDB down? Metrics still flow to the event stream. This keeps the system partially functional during partial failures.
|
||||
|
||||
### Configuration
|
||||
|
||||
All configuration uses Pydantic with environment variable support (`shared/config.py`):
|
||||
|
||||
```python
|
||||
class CollectorConfig(BaseSettings):
|
||||
machine_id: str = Field(default_factory=lambda: socket.gethostname())
|
||||
aggregator_url: str = "aggregator:50051"
|
||||
collection_interval: int = 5
|
||||
|
||||
model_config = SettingsConfigDict(env_prefix="COLLECTOR_")
|
||||
```
|
||||
|
||||
Set `COLLECTOR_AGGREGATOR_URL=192.168.1.100:50051` and it overrides the default. No code changes for different environments.
|
||||
|
||||
## What Worked
|
||||
|
||||
**The event abstraction.** Adding a new consumer (like the gateway's WebSocket bridge) required zero changes to the aggregator. Subscribe to the topic, process events.
|
||||
|
||||
**Tiered storage.** Redis handles the hot path (dashboard reads). TimescaleDB handles history. Each optimized for its access pattern.
|
||||
|
||||
**Graceful degradation.** During development, I regularly restarted individual services. The system stayed partially functional throughout.
|
||||
|
||||
## What Could Be Better
|
||||
|
||||
**No backpressure.** If the aggregator falls behind, events accumulate in memory. A production system would need flow control.
|
||||
|
||||
**Alert rules are database-only.** Changing thresholds requires database updates. A proper config management system would be better.
|
||||
|
||||
**No authentication.** The gRPC channels are insecure. Production would need TLS and service authentication.
|
||||
|
||||
## Key Files Reference
|
||||
|
||||
| Component | File | Purpose |
|
||||
|-----------|------|---------|
|
||||
| Proto definitions | `proto/metrics.proto` | gRPC service and message definitions |
|
||||
| Collector main | `services/collector/main.py` | gRPC client, streaming logic |
|
||||
| Metric collection | `services/collector/metrics.py` | psutil wrappers |
|
||||
| Aggregator main | `services/aggregator/main.py` | gRPC server, batch processing |
|
||||
| Storage layer | `services/aggregator/storage.py` | Redis + TimescaleDB abstraction |
|
||||
| Gateway main | `services/gateway/main.py` | FastAPI, WebSocket, event bridge |
|
||||
| Alerts main | `services/alerts/main.py` | Event subscription, rule evaluation |
|
||||
| Event abstraction | `shared/events/base.py` | Publisher/subscriber interfaces |
|
||||
| Redis events | `shared/events/redis_pubsub.py` | Redis Pub/Sub implementation |
|
||||
| Configuration | `shared/config.py` | Pydantic settings for all services |
|
||||
| DB initialization | `scripts/init-db.sql` | TimescaleDB schema, hypertables |
|
||||
| Docker setup | `docker-compose.yml` | Full stack orchestration |
|
||||
|
||||
## Running It
|
||||
|
||||
```bash
|
||||
docker compose up
|
||||
```
|
||||
|
||||
Open `http://localhost:8000` for the dashboard. Metrics appear within seconds.
|
||||
|
||||
To add another machine, run the collector pointed at your aggregator:
|
||||
|
||||
```bash
|
||||
COLLECTOR_AGGREGATOR_URL=your-server:50051 python services/collector/main.py
|
||||
```
|
||||
|
||||
It connects, starts streaming, and appears on the dashboard.
|
||||
425
atlas/books/sysmonstm/explainer/viewer.html
Normal 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, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>');
|
||||
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>
|
||||
393
atlas/books/sysmonstm/index.html
Normal file
@@ -0,0 +1,393 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>System Monitor - Documentation</title>
|
||||
<link rel="stylesheet" href="architecture/styles.css" />
|
||||
<style>
|
||||
/* Additional styles for docs index */
|
||||
.nav-section {
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.nav-section h2 {
|
||||
color: var(--accent);
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.doc-links {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.doc-link {
|
||||
display: block;
|
||||
background: var(--bg-card);
|
||||
padding: 1.25rem;
|
||||
border-radius: 8px;
|
||||
text-decoration: none;
|
||||
border: 1px solid var(--border);
|
||||
transition:
|
||||
border-color 0.2s,
|
||||
transform 0.2s;
|
||||
}
|
||||
|
||||
.doc-link:hover {
|
||||
border-color: var(--accent);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.doc-link h3 {
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.doc-link p {
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.doc-link .tag {
|
||||
display: inline-block;
|
||||
background: var(--accent-secondary);
|
||||
color: var(--text-primary);
|
||||
padding: 0.125rem 0.5rem;
|
||||
border-radius: 3px;
|
||||
font-size: 0.75rem;
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
.section-divider {
|
||||
border: none;
|
||||
border-top: 1px solid var(--border);
|
||||
margin: 2rem 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>System Monitoring Platform</h1>
|
||||
<p class="subtitle">Documentation</p>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<!-- Explainer Articles -->
|
||||
<section class="nav-section">
|
||||
<h2>Explainer Articles</h2>
|
||||
<div class="doc-links">
|
||||
<a
|
||||
href="explainer/viewer.html?file=sysmonstm-from-start-to-finish.md"
|
||||
class="doc-link"
|
||||
>
|
||||
<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>
|
||||
<span class="tag">Article</span>
|
||||
</a>
|
||||
<a
|
||||
href="explainer/viewer.html?file=other-applications.md"
|
||||
class="doc-link"
|
||||
>
|
||||
<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>
|
||||
<span class="tag">Article</span>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<hr class="section-divider" />
|
||||
|
||||
<!-- Architecture Diagrams -->
|
||||
<section class="graph-section" id="overview">
|
||||
<div class="graph-header-row">
|
||||
<h2>System Overview</h2>
|
||||
<a
|
||||
href="architecture/graph.html?g=01-system-overview"
|
||||
class="view-btn"
|
||||
>View Full</a
|
||||
>
|
||||
</div>
|
||||
<a
|
||||
href="architecture/graph.html?g=01-system-overview"
|
||||
class="graph-preview"
|
||||
>
|
||||
<img
|
||||
src="architecture/01-system-overview.svg"
|
||||
alt="System Overview"
|
||||
/>
|
||||
</a>
|
||||
<div class="graph-details">
|
||||
<p>
|
||||
High-level architecture showing all services, data
|
||||
stores, and communication patterns.
|
||||
</p>
|
||||
<h4>Key Components</h4>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>Collector</strong>: Runs on each monitored
|
||||
machine, streams metrics via gRPC
|
||||
</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>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="graph-section" id="data-flow">
|
||||
<div class="graph-header-row">
|
||||
<h2>Data Flow Pipeline</h2>
|
||||
<a
|
||||
href="architecture/graph.html?g=02-data-flow"
|
||||
class="view-btn"
|
||||
>View Full</a
|
||||
>
|
||||
</div>
|
||||
<a
|
||||
href="architecture/graph.html?g=02-data-flow"
|
||||
class="graph-preview"
|
||||
>
|
||||
<img src="architecture/02-data-flow.svg" alt="Data Flow" />
|
||||
</a>
|
||||
<div class="graph-details">
|
||||
<p>
|
||||
How metrics flow from collection through storage with
|
||||
different retention tiers.
|
||||
</p>
|
||||
<h4>Storage Tiers</h4>
|
||||
<table class="details-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Tier</th>
|
||||
<th>Resolution</th>
|
||||
<th>Retention</th>
|
||||
<th>Use Case</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Hot (Redis)</td>
|
||||
<td>5s</td>
|
||||
<td>5 min</td>
|
||||
<td>Current state, live dashboard</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Raw (TimescaleDB)</td>
|
||||
<td>5s</td>
|
||||
<td>24h</td>
|
||||
<td>Recent detailed analysis</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1-min Aggregates</td>
|
||||
<td>1m</td>
|
||||
<td>7d</td>
|
||||
<td>Week view, trends</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1-hour Aggregates</td>
|
||||
<td>1h</td>
|
||||
<td>90d</td>
|
||||
<td>Long-term analysis</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="graph-section" id="deployment">
|
||||
<div class="graph-header-row">
|
||||
<h2>Deployment Architecture</h2>
|
||||
<a
|
||||
href="architecture/graph.html?g=03-deployment"
|
||||
class="view-btn"
|
||||
>View Full</a
|
||||
>
|
||||
</div>
|
||||
<a
|
||||
href="architecture/graph.html?g=03-deployment"
|
||||
class="graph-preview"
|
||||
>
|
||||
<img
|
||||
src="architecture/03-deployment.svg"
|
||||
alt="Deployment"
|
||||
/>
|
||||
</a>
|
||||
<div class="graph-details">
|
||||
<p>
|
||||
Deployment options from local development to AWS
|
||||
production.
|
||||
</p>
|
||||
<h4>Environments</h4>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>Local Dev</strong>: Kind + Tilt for K8s, or
|
||||
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>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="graph-section" id="grpc">
|
||||
<div class="graph-header-row">
|
||||
<h2>gRPC Service Definitions</h2>
|
||||
<a
|
||||
href="architecture/graph.html?g=04-grpc-services"
|
||||
class="view-btn"
|
||||
>View Full</a
|
||||
>
|
||||
</div>
|
||||
<a
|
||||
href="architecture/graph.html?g=04-grpc-services"
|
||||
class="graph-preview"
|
||||
>
|
||||
<img
|
||||
src="architecture/04-grpc-services.svg"
|
||||
alt="gRPC Services"
|
||||
/>
|
||||
</a>
|
||||
<div class="graph-details">
|
||||
<p>Protocol Buffer service and message definitions.</p>
|
||||
<h4>Services</h4>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>MetricsService</strong>: Client-side
|
||||
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>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<hr class="section-divider" />
|
||||
|
||||
<section class="findings-section">
|
||||
<h2>Interview Talking Points</h2>
|
||||
<div class="findings-grid">
|
||||
<article class="finding-card">
|
||||
<h3>Domain Mapping</h3>
|
||||
<ul>
|
||||
<li>Machine = Payment Processor</li>
|
||||
<li>Metrics Stream = Transaction Stream</li>
|
||||
<li>Thresholds = Fraud Detection</li>
|
||||
<li>Aggregator = Payment Hub</li>
|
||||
</ul>
|
||||
</article>
|
||||
<article class="finding-card">
|
||||
<h3>gRPC Patterns</h3>
|
||||
<ul>
|
||||
<li>Client streaming (metrics)</li>
|
||||
<li>Server streaming (config)</li>
|
||||
<li>Bidirectional (control)</li>
|
||||
<li>Health checking</li>
|
||||
</ul>
|
||||
</article>
|
||||
<article class="finding-card">
|
||||
<h3>Event-Driven</h3>
|
||||
<ul>
|
||||
<li>Redis Pub/Sub (current)</li>
|
||||
<li>Abstraction for Kafka switch</li>
|
||||
<li>Decoupled alert processing</li>
|
||||
<li>Real-time WebSocket push</li>
|
||||
</ul>
|
||||
</article>
|
||||
<article class="finding-card">
|
||||
<h3>Resilience</h3>
|
||||
<ul>
|
||||
<li>Collectors are independent</li>
|
||||
<li>Graceful degradation</li>
|
||||
<li>Retry with backoff</li>
|
||||
<li>Health checks everywhere</li>
|
||||
</ul>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="tech-section">
|
||||
<h2>Technology Stack</h2>
|
||||
<div class="tech-grid">
|
||||
<div class="tech-column">
|
||||
<h3>Core</h3>
|
||||
<ul>
|
||||
<li>Python 3.11+</li>
|
||||
<li>FastAPI</li>
|
||||
<li>gRPC / protobuf</li>
|
||||
<li>asyncio</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tech-column">
|
||||
<h3>Data</h3>
|
||||
<ul>
|
||||
<li>TimescaleDB</li>
|
||||
<li>Redis</li>
|
||||
<li>Redis Pub/Sub</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tech-column">
|
||||
<h3>Infrastructure</h3>
|
||||
<ul>
|
||||
<li>Docker</li>
|
||||
<li>Kubernetes</li>
|
||||
<li>Kind + Tilt</li>
|
||||
<li>Terraform</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tech-column">
|
||||
<h3>CI/CD</h3>
|
||||
<ul>
|
||||
<li>Woodpecker CI</li>
|
||||
<li>Kustomize</li>
|
||||
<li>Container Registry</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>System Monitoring Platform - Documentation</p>
|
||||
<p class="date">
|
||||
Generated: <time datetime="2025-12-31">December 2025</time>
|
||||
</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
1
atlas/books/sysmonstm/static/prism/prism-bash.min.js
vendored
Normal file
1
atlas/books/sysmonstm/static/prism/prism-gherkin.min.js
vendored
Normal file
1
atlas/books/sysmonstm/static/prism/prism-json.min.js
vendored
Normal 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;
|
||||
1
atlas/books/sysmonstm/static/prism/prism-line-numbers.min.css
vendored
Normal 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}
|
||||
1
atlas/books/sysmonstm/static/prism/prism-line-numbers.min.js
vendored
Normal 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"}))}))}}}();
|
||||
1
atlas/books/sysmonstm/static/prism/prism-protobuf.min.js
vendored
Normal 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
atlas/books/sysmonstm/static/prism/prism-python.min.js
vendored
Normal 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://,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;
|
||||
1
atlas/books/sysmonstm/static/prism/prism-tomorrow.min.css
vendored
Normal 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}
|
||||