This commit is contained in:
buenosairesam
2025-12-31 05:07:53 -03:00
parent 00b1e663d9
commit 040fccc58d
6 changed files with 1363 additions and 316 deletions

View File

@@ -1,120 +1,130 @@
<!DOCTYPE html>
<!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>
<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>
<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",
];
<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 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();
}
};
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 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 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';
function navigate(direction) {
const idx = graphOrder.indexOf(graphKey);
const newIdx = idx + direction;
if (newIdx >= 0 && newIdx < graphOrder.length) {
currentIndex = newIdx;
loadGraph(graphOrder[newIdx]);
}
}
});
// Initialize
loadGraph(graphOrder[currentIndex]);
setMode('fit');
</script>
</body>
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>

View File

@@ -1,207 +1,262 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>System Monitor - Architecture Documentation</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<header>
<h1>System Monitoring Platform</h1>
<p class="subtitle">Architecture & Design Documentation</p>
</header>
<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>
<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>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>
<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>
<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>
</section>
<footer>
<p>System Monitoring Platform - Architecture Documentation</p>
<p class="date">Generated: <time datetime="2025-12-29">December 2025</time></p>
</footer>
</body>
<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>