Compare commits
17 Commits
79ccae8a6e
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| a15de4f72b | |||
| 8c6ec2e683 | |||
| 6dc3c01637 | |||
| 0f60556e81 | |||
|
|
91f95d55a5 | ||
|
|
3106bc835e | ||
|
|
a013e0116f | ||
|
|
8ecd702b63 | ||
|
|
2da4b30019 | ||
|
|
754d3e55fb | ||
|
|
761bca20b0 | ||
|
|
9ddcb68131 | ||
|
|
f854d6d399 | ||
|
|
0cd8d1516f | ||
|
|
82c4551e71 | ||
|
|
dc3518f138 | ||
|
|
174bc15368 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1 +1,5 @@
|
|||||||
def
|
def
|
||||||
|
.env
|
||||||
|
ctrl/.env
|
||||||
|
|
||||||
|
#
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
# sysmonstm Pipeline
|
|
||||||
|
|
||||||
when:
|
|
||||||
- event: push
|
|
||||||
- event: manual
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: build
|
|
||||||
image: plugins/docker
|
|
||||||
settings:
|
|
||||||
repo: registry.mcrn.ar/sysmonstm
|
|
||||||
registry: registry.mcrn.ar
|
|
||||||
tags:
|
|
||||||
- latest
|
|
||||||
- ${CI_COMMIT_SHA:0:7}
|
|
||||||
dockerfile: ctrl/standalone/Dockerfile
|
|
||||||
context: ctrl/standalone
|
|
||||||
25
.woodpecker/build.yml
Normal file
25
.woodpecker/build.yml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# sysmonstm Pipeline
|
||||||
|
|
||||||
|
when:
|
||||||
|
- event: push
|
||||||
|
- event: manual
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: build
|
||||||
|
image: plugins/docker
|
||||||
|
settings:
|
||||||
|
repo: registry.mcrn.ar/sysmonstm/edge
|
||||||
|
registry: registry.mcrn.ar
|
||||||
|
tags:
|
||||||
|
- latest
|
||||||
|
- ${CI_COMMIT_SHA:0:7}
|
||||||
|
dockerfile: ctrl/edge/Dockerfile
|
||||||
|
context: ctrl/edge
|
||||||
|
|
||||||
|
- name: deploy
|
||||||
|
image: docker:24-cli
|
||||||
|
commands:
|
||||||
|
- docker pull registry.mcrn.ar/sysmonstm/edge:latest
|
||||||
|
- docker stop sysmonstm-edge || true
|
||||||
|
- docker rm sysmonstm-edge || true
|
||||||
|
- docker run -d --name sysmonstm-edge --restart unless-stopped --network gateway -p 8080:8080 registry.mcrn.ar/sysmonstm/edge:latest
|
||||||
533
CLAUDE.md
533
CLAUDE.md
@@ -2,491 +2,90 @@
|
|||||||
|
|
||||||
## Project Overview
|
## Project Overview
|
||||||
|
|
||||||
A real-time system monitoring platform that streams metrics from multiple machines to a central hub with live web dashboard. Built to demonstrate production microservices patterns (gRPC, FastAPI, streaming, event-driven architecture) while solving a real problem: monitoring development infrastructure across multiple machines.
|
A real-time system monitoring platform that streams metrics from multiple machines to a central hub with live web dashboard. Built to demonstrate production microservices patterns (gRPC, FastAPI, streaming, event-driven architecture).
|
||||||
|
|
||||||
**Primary Goal:** Interview demonstration project for Python Microservices Engineer position
|
**Primary Goal:** Portfolio project demonstrating real-time streaming with gRPC
|
||||||
**Secondary Goal:** Actually useful tool for managing multi-machine development environment
|
**Status:** Working, deployed at sysmonstm.mcrn.ar
|
||||||
**Time Investment:** Phased approach - MVP in weekend, polish over 2-3 weeks
|
|
||||||
|
|
||||||
## Why This Project
|
|
||||||
|
|
||||||
**Interview Alignment:**
|
|
||||||
- Demonstrates gRPC-based microservices architecture (core requirement)
|
|
||||||
- Shows streaming patterns (server-side and bidirectional)
|
|
||||||
- Real-time data aggregation and processing
|
|
||||||
- Alert/threshold monitoring (maps to fraud detection)
|
|
||||||
- Event-driven patterns
|
|
||||||
- Multiple data sources requiring normalization (maps to multiple payment processors)
|
|
||||||
|
|
||||||
**Personal Utility:**
|
|
||||||
- Monitors existing multi-machine dev setup
|
|
||||||
- Dashboard stays open, provides real value
|
|
||||||
- Solves actual pain point
|
|
||||||
- Will continue running post-interview
|
|
||||||
|
|
||||||
**Domain Mapping for Interview:**
|
|
||||||
- Machine = Payment Processor
|
|
||||||
- Metrics Stream = Transaction Stream
|
|
||||||
- Resource Thresholds = Fraud/Limit Detection
|
|
||||||
- Alert System = Risk Management
|
|
||||||
- Aggregation Service = Payment Processing Hub
|
|
||||||
|
|
||||||
## Technical Stack
|
|
||||||
|
|
||||||
### Core Technologies (Must Use - From JD)
|
|
||||||
- **Python 3.11+** - Primary language
|
|
||||||
- **FastAPI** - Web gateway, REST endpoints, WebSocket streaming
|
|
||||||
- **gRPC** - Inter-service communication, metric streaming
|
|
||||||
- **PostgreSQL/TimescaleDB** - Time-series historical data
|
|
||||||
- **Redis** - Current state, caching, alert rules
|
|
||||||
- **Docker Compose** - Orchestration
|
|
||||||
|
|
||||||
### Supporting Technologies
|
|
||||||
- **Protocol Buffers** - gRPC message definitions
|
|
||||||
- **WebSockets** - Browser streaming
|
|
||||||
- **htmx + Alpine.js** - Lightweight reactive frontend (avoid heavy SPA)
|
|
||||||
- **Chart.js or Apache ECharts** - Real-time graphs
|
|
||||||
- **asyncio** - Async patterns throughout
|
|
||||||
|
|
||||||
### Development Tools
|
|
||||||
- **grpcio & grpcio-tools** - Python gRPC
|
|
||||||
- **psutil** - System metrics collection
|
|
||||||
- **uvicorn** - FastAPI server
|
|
||||||
- **pytest** - Testing
|
|
||||||
- **docker-compose** - Local orchestration
|
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
```
|
```
|
||||||
┌─────────────────────────────────────────────────────────────┐
|
┌─────────────┐ ┌─────────────────────────────────────┐ ┌─────────────┐
|
||||||
│ Browser │
|
│ Collector │────▶│ Aggregator + Gateway + Redis + TS │────▶│ Edge │────▶ Browser
|
||||||
│ ┌──────────────────────────────────────────────────────┐ │
|
│ (mcrn) │gRPC │ (LOCAL) │ WS │ (AWS) │ WS
|
||||||
│ │ Dashboard (htmx + Alpine.js + WebSockets) │ │
|
└─────────────┘ └─────────────────────────────────────┘ └─────────────┘
|
||||||
│ └──────────────────────────────────────────────────────┘ │
|
┌─────────────┐ │
|
||||||
└────────────────────────┬────────────────────────────────────┘
|
│ Collector │────────────────────┘
|
||||||
│ WebSocket
|
│ (nfrt) │gRPC
|
||||||
▼
|
└─────────────┘
|
||||||
┌─────────────────────────────────────────────────────────────┐
|
```
|
||||||
│ Web Gateway Service │
|
|
||||||
│ (FastAPI + WebSockets) │
|
- **Collectors** (`services/collector/`) - gRPC clients on each monitored machine
|
||||||
│ - Serves dashboard │
|
- **Aggregator** (`services/aggregator/`) - gRPC server, stores in Redis/TimescaleDB
|
||||||
│ - Streams updates to browser │
|
- **Gateway** (`services/gateway/`) - FastAPI, bridges gRPC to WebSocket, forwards to edge
|
||||||
│ - REST API for historical queries │
|
- **Edge** (`ctrl/edge/`) - Simple WebSocket relay for AWS, serves public dashboard
|
||||||
└────────────────────────┬────────────────────────────────────┘
|
|
||||||
│ gRPC
|
## Directory Structure
|
||||||
▼
|
|
||||||
┌─────────────────────────────────────────────────────────────┐
|
```
|
||||||
│ Aggregator Service (gRPC) │
|
sms/
|
||||||
│ - Receives metric streams from all collectors │
|
├── services/ # gRPC-based microservices
|
||||||
│ - Normalizes data from different sources │
|
│ ├── collector/ # gRPC client, streams to aggregator
|
||||||
│ - Enriches with machine context │
|
│ ├── aggregator/ # gRPC server, stores in Redis/TimescaleDB
|
||||||
│ - Publishes to event stream │
|
│ ├── gateway/ # FastAPI, WebSocket, forwards to edge
|
||||||
│ - Checks alert thresholds │
|
│ └── alerts/ # Event subscriber for threshold alerts
|
||||||
└─────┬───────────────────────────────────┬───────────────────┘
|
|
||||||
│ │
|
|
||||||
│ Stores │ Publishes events
|
|
||||||
▼ ▼
|
|
||||||
┌──────────────┐ ┌────────────────┐
|
|
||||||
│ TimescaleDB │ │ Event Stream │
|
|
||||||
│ (historical)│ │ (Redis Pub/Sub│
|
|
||||||
└──────────────┘ │ or RabbitMQ) │
|
|
||||||
└────────┬───────┘
|
|
||||||
┌──────────────┐ │
|
|
||||||
│ Redis │ │ Subscribes
|
|
||||||
│ (current │◄───────────────────────────┘
|
|
||||||
│ state) │ │
|
|
||||||
└──────────────┘ ▼
|
|
||||||
┌────────────────┐
|
|
||||||
▲ │ Alert Service │
|
|
||||||
│ │ - Processes │
|
|
||||||
│ │ events │
|
|
||||||
│ gRPC Streaming │ - Triggers │
|
|
||||||
│ │ actions │
|
|
||||||
┌─────┴────────────────────────────┴────────────────┘
|
|
||||||
│
|
│
|
||||||
│ Multiple Collector Services (one per machine)
|
├── ctrl/ # Deployment configurations
|
||||||
│ ┌───────────────────────────────────────┐
|
│ ├── dev/ # Full stack docker-compose
|
||||||
│ │ Metrics Collector (gRPC Client) │
|
│ └── edge/ # Cloud dashboard (AWS)
|
||||||
│ │ - Gathers system metrics (psutil) │
|
|
||||||
│ │ - Streams to Aggregator via gRPC │
|
|
||||||
│ │ - CPU, Memory, Disk, Network │
|
|
||||||
│ │ - Process list │
|
|
||||||
│ │ - Docker container stats (optional) │
|
|
||||||
│ └───────────────────────────────────────┘
|
|
||||||
│
|
│
|
||||||
└──► Machine 1, Machine 2, Machine 3, ...
|
├── proto/ # Protocol Buffer definitions
|
||||||
|
├── shared/ # Shared Python modules (config, logging, events)
|
||||||
|
└── web/ # Dashboard templates and static files
|
||||||
```
|
```
|
||||||
|
|
||||||
## Implementation Phases
|
## Running
|
||||||
|
|
||||||
### Phase 1: MVP - Core Streaming (Weekend - 8-12 hours)
|
### Local Development
|
||||||
|
```bash
|
||||||
**Goal:** Prove the gRPC streaming works end-to-end
|
docker compose up
|
||||||
|
|
||||||
**Deliverables:**
|
|
||||||
1. Metrics Collector Service (gRPC client)
|
|
||||||
- Collects CPU, memory, disk on localhost
|
|
||||||
- Streams to aggregator every 5 seconds
|
|
||||||
|
|
||||||
2. Aggregator Service (gRPC server)
|
|
||||||
- Receives metric stream
|
|
||||||
- Stores current state in Redis
|
|
||||||
- Logs to console
|
|
||||||
|
|
||||||
3. Proto definitions for metric messages
|
|
||||||
|
|
||||||
4. Docker Compose setup
|
|
||||||
|
|
||||||
**Success Criteria:**
|
|
||||||
- Run collector, see metrics flowing to aggregator
|
|
||||||
- Redis contains current state
|
|
||||||
- Can query Redis manually for latest metrics
|
|
||||||
|
|
||||||
### Phase 2: Web Dashboard (1 week)
|
|
||||||
|
|
||||||
**Goal:** Make it visible and useful
|
|
||||||
|
|
||||||
**Deliverables:**
|
|
||||||
1. Web Gateway Service (FastAPI)
|
|
||||||
- WebSocket endpoint for streaming
|
|
||||||
- REST endpoints for current/historical data
|
|
||||||
|
|
||||||
2. Dashboard UI
|
|
||||||
- Real-time CPU/Memory graphs per machine
|
|
||||||
- Current state table
|
|
||||||
- Simple, clean design
|
|
||||||
|
|
||||||
3. WebSocket bridge (Gateway ↔ Aggregator)
|
|
||||||
|
|
||||||
4. TimescaleDB integration
|
|
||||||
- Store historical metrics
|
|
||||||
- Query endpoints for time ranges
|
|
||||||
|
|
||||||
**Success Criteria:**
|
|
||||||
- Open dashboard, see live graphs updating
|
|
||||||
- Graphs show last hour of data
|
|
||||||
- Multiple machines displayed separately
|
|
||||||
|
|
||||||
### Phase 3: Alerts & Intelligence (1 week)
|
|
||||||
|
|
||||||
**Goal:** Add decision-making layer (interview focus)
|
|
||||||
|
|
||||||
**Deliverables:**
|
|
||||||
1. Alert Service
|
|
||||||
- Subscribes to event stream
|
|
||||||
- Evaluates threshold rules
|
|
||||||
- Triggers notifications
|
|
||||||
|
|
||||||
2. Configuration Service (gRPC)
|
|
||||||
- Dynamic threshold management
|
|
||||||
- Alert rule CRUD
|
|
||||||
- Stored in PostgreSQL
|
|
||||||
|
|
||||||
3. Event Stream implementation (Redis Pub/Sub or RabbitMQ)
|
|
||||||
|
|
||||||
4. Enhanced dashboard
|
|
||||||
- Alert indicators
|
|
||||||
- Alert history
|
|
||||||
- Threshold configuration UI
|
|
||||||
|
|
||||||
**Success Criteria:**
|
|
||||||
- Set CPU threshold at 80%
|
|
||||||
- Generate load (stress-ng)
|
|
||||||
- See alert trigger in dashboard
|
|
||||||
- Alert logged to database
|
|
||||||
|
|
||||||
### Phase 4: Interview Polish (Final week)
|
|
||||||
|
|
||||||
**Goal:** Demo-ready, production patterns visible
|
|
||||||
|
|
||||||
**Deliverables:**
|
|
||||||
1. Observability
|
|
||||||
- OpenTelemetry tracing (optional)
|
|
||||||
- Structured logging
|
|
||||||
- Health check endpoints
|
|
||||||
|
|
||||||
2. "Synthetic Transactions"
|
|
||||||
- Simulate business operations through system
|
|
||||||
- Track end-to-end latency
|
|
||||||
- Maps directly to payment processing demo
|
|
||||||
|
|
||||||
3. Documentation
|
|
||||||
- Architecture diagram
|
|
||||||
- Service interaction flows
|
|
||||||
- Deployment guide
|
|
||||||
|
|
||||||
4. Demo script
|
|
||||||
- Story to walk through
|
|
||||||
- Key talking points
|
|
||||||
- Domain mapping explanations
|
|
||||||
|
|
||||||
**Success Criteria:**
|
|
||||||
- Can deploy entire stack with one command
|
|
||||||
- Can explain every service's role
|
|
||||||
- Can map architecture to payment processing
|
|
||||||
- Demo runs smoothly without hiccups
|
|
||||||
|
|
||||||
## Key Technical Patterns to Demonstrate
|
|
||||||
|
|
||||||
### 1. gRPC Streaming Patterns
|
|
||||||
|
|
||||||
**Server-Side Streaming:**
|
|
||||||
```python
|
|
||||||
# Collector streams metrics to aggregator
|
|
||||||
service MetricsService {
|
|
||||||
rpc StreamMetrics(MetricsRequest) returns (stream Metric) {}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Bidirectional Streaming:**
|
### With Edge Forwarding (to AWS)
|
||||||
```python
|
```bash
|
||||||
# Two-way communication between services
|
EDGE_URL=wss://sysmonstm.mcrn.ar/ws docker compose up
|
||||||
service ControlService {
|
|
||||||
rpc ManageStream(stream Command) returns (stream Response) {}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Service Communication Patterns
|
### Collector on Remote Machine
|
||||||
|
```bash
|
||||||
- **Synchronous (gRPC):** Query current state, configuration
|
docker run -d --network host \
|
||||||
- **Asynchronous (Events):** Metric updates, alerts, audit logs
|
-e AGGREGATOR_URL=<local-ip>:50051 \
|
||||||
- **Streaming (gRPC + WebSocket):** Real-time data flow
|
-e MACHINE_ID=$(hostname) \
|
||||||
|
registry.mcrn.ar/sysmonstm/collector:latest
|
||||||
### 3. Data Storage Patterns
|
|
||||||
|
|
||||||
- **Hot data (Redis):** Current state, recent metrics (last 5 minutes)
|
|
||||||
- **Warm data (TimescaleDB):** Historical metrics (last 30 days)
|
|
||||||
- **Cold data (Optional):** Archive to S3-compatible storage
|
|
||||||
|
|
||||||
### 4. Error Handling & Resilience
|
|
||||||
|
|
||||||
- gRPC retry logic with exponential backoff
|
|
||||||
- Circuit breaker pattern for service calls
|
|
||||||
- Graceful degradation (continue if one collector fails)
|
|
||||||
- Dead letter queue for failed events
|
|
||||||
|
|
||||||
## Proto Definitions (Starting Point)
|
|
||||||
|
|
||||||
```protobuf
|
|
||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package monitoring;
|
|
||||||
|
|
||||||
service MetricsService {
|
|
||||||
rpc StreamMetrics(MetricsRequest) returns (stream Metric) {}
|
|
||||||
rpc GetCurrentState(StateRequest) returns (MachineState) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
message MetricsRequest {
|
|
||||||
string machine_id = 1;
|
|
||||||
int32 interval_seconds = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Metric {
|
|
||||||
string machine_id = 1;
|
|
||||||
int64 timestamp = 2;
|
|
||||||
MetricType type = 3;
|
|
||||||
double value = 4;
|
|
||||||
map<string, string> labels = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum MetricType {
|
|
||||||
CPU_PERCENT = 0;
|
|
||||||
MEMORY_PERCENT = 1;
|
|
||||||
MEMORY_USED_GB = 2;
|
|
||||||
DISK_PERCENT = 3;
|
|
||||||
NETWORK_SENT_MBPS = 4;
|
|
||||||
NETWORK_RECV_MBPS = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
message MachineState {
|
|
||||||
string machine_id = 1;
|
|
||||||
int64 last_seen = 2;
|
|
||||||
repeated Metric current_metrics = 3;
|
|
||||||
HealthStatus health = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum HealthStatus {
|
|
||||||
HEALTHY = 0;
|
|
||||||
WARNING = 1;
|
|
||||||
CRITICAL = 2;
|
|
||||||
UNKNOWN = 3;
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Project Structure
|
## Technical Stack
|
||||||
|
|
||||||
```
|
- **Python 3.11+**
|
||||||
system-monitor/
|
- **gRPC** - Collector to aggregator communication (showcased)
|
||||||
├── docker-compose.yml
|
- **FastAPI** - Gateway REST/WebSocket
|
||||||
├── proto/
|
- **Redis** - Pub/Sub events, current state cache
|
||||||
│ └── metrics.proto
|
- **TimescaleDB** - Historical metrics storage
|
||||||
├── services/
|
- **WebSocket** - Gateway to edge, edge to browser
|
||||||
│ ├── collector/
|
|
||||||
│ │ ├── Dockerfile
|
|
||||||
│ │ ├── requirements.txt
|
|
||||||
│ │ ├── main.py
|
|
||||||
│ │ └── metrics.py
|
|
||||||
│ ├── aggregator/
|
|
||||||
│ │ ├── Dockerfile
|
|
||||||
│ │ ├── requirements.txt
|
|
||||||
│ │ ├── main.py
|
|
||||||
│ │ └── storage.py
|
|
||||||
│ ├── gateway/
|
|
||||||
│ │ ├── Dockerfile
|
|
||||||
│ │ ├── requirements.txt
|
|
||||||
│ │ ├── main.py
|
|
||||||
│ │ └── websocket.py
|
|
||||||
│ └── alerts/
|
|
||||||
│ ├── Dockerfile
|
|
||||||
│ ├── requirements.txt
|
|
||||||
│ ├── main.py
|
|
||||||
│ └── rules.py
|
|
||||||
├── web/
|
|
||||||
│ ├── static/
|
|
||||||
│ │ ├── css/
|
|
||||||
│ │ └── js/
|
|
||||||
│ └── templates/
|
|
||||||
│ └── dashboard.html
|
|
||||||
└── README.md
|
|
||||||
```
|
|
||||||
|
|
||||||
## Interview Talking Points
|
## Key Files
|
||||||
|
|
||||||
### Domain Mapping to Payments
|
| File | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `proto/metrics.proto` | gRPC service and message definitions |
|
||||||
|
| `services/collector/main.py` | gRPC streaming client |
|
||||||
|
| `services/aggregator/main.py` | gRPC server, metric processing |
|
||||||
|
| `services/gateway/main.py` | WebSocket bridge, edge forwarding |
|
||||||
|
| `ctrl/edge/edge.py` | Simple WebSocket relay for AWS |
|
||||||
|
|
||||||
**What you say:**
|
## Portfolio Talking Points
|
||||||
- "I built this to monitor my dev machines, but the architecture directly maps to payment processing"
|
|
||||||
- "Each machine streaming metrics is like a payment processor streaming transactions"
|
|
||||||
- "The aggregator normalizes data from different sources - same as aggregating from Stripe, PayPal, bank APIs"
|
|
||||||
- "Alert thresholds on resource usage are structurally identical to fraud detection thresholds"
|
|
||||||
- "The event stream for audit trails maps directly to payment audit logs"
|
|
||||||
|
|
||||||
### Technical Decisions to Highlight
|
- **gRPC streaming** - Efficient binary protocol for real-time metrics
|
||||||
|
- **Event-driven** - Redis Pub/Sub decouples processing from delivery
|
||||||
**gRPC vs REST:**
|
- **Edge pattern** - Heavy processing local, lightweight relay in cloud
|
||||||
- "I use gRPC between services for efficiency and strong typing"
|
- **Cost optimization** - ~$10/mo for public dashboard (data transfer, not requests)
|
||||||
- "FastAPI gateway exposes REST/WebSocket for browser clients"
|
|
||||||
- "This pattern is common - internal gRPC, external REST"
|
|
||||||
|
|
||||||
**Streaming vs Polling:**
|
|
||||||
- "Server-side streaming reduces network overhead"
|
|
||||||
- "Bidirectional streaming allows dynamic configuration updates"
|
|
||||||
- "WebSocket to browser maintains single connection"
|
|
||||||
|
|
||||||
**State Management:**
|
|
||||||
- "Redis for hot data - current state, needs fast access"
|
|
||||||
- "TimescaleDB for historical analysis - optimized for time-series"
|
|
||||||
- "This tiered storage approach scales to payment transaction volumes"
|
|
||||||
|
|
||||||
**Resilience:**
|
|
||||||
- "Each collector is independent - one failing doesn't affect others"
|
|
||||||
- "Circuit breaker prevents cascade failures"
|
|
||||||
- "Event stream decouples alert processing from metric ingestion"
|
|
||||||
|
|
||||||
### What NOT to Say
|
|
||||||
|
|
||||||
- Don't call it a "toy project" or "learning exercise"
|
|
||||||
- Don't apologize for running locally vs AWS
|
|
||||||
- Don't over-explain obvious things
|
|
||||||
- Don't claim it's production-ready when it's not
|
|
||||||
|
|
||||||
### What TO Say
|
|
||||||
|
|
||||||
- "I built this to solve a real problem I have"
|
|
||||||
- "Locally it uses PostgreSQL/Redis, in production these become Aurora/ElastiCache"
|
|
||||||
- "I focused on the architectural patterns since those transfer directly"
|
|
||||||
- "I'd keep developing this - it's genuinely useful"
|
|
||||||
|
|
||||||
## Development Guidelines
|
|
||||||
|
|
||||||
### Code Quality Standards
|
|
||||||
- Type hints throughout (Python 3.11+ syntax)
|
|
||||||
- Async/await patterns consistently
|
|
||||||
- Structured logging (JSON format)
|
|
||||||
- Error handling at all boundaries
|
|
||||||
- Unit tests for business logic
|
|
||||||
- Integration tests for service interactions
|
|
||||||
|
|
||||||
### Docker Best Practices
|
|
||||||
- Multi-stage builds
|
|
||||||
- Non-root users
|
|
||||||
- Health checks
|
|
||||||
- Resource limits
|
|
||||||
- Volume mounts for development
|
|
||||||
|
|
||||||
### Configuration Management
|
|
||||||
- Environment variables for all config
|
|
||||||
- Sensible defaults
|
|
||||||
- Config validation on startup
|
|
||||||
- No secrets in code
|
|
||||||
|
|
||||||
## AWS Mapping (For Interview Discussion)
|
|
||||||
|
|
||||||
**What you have → What it becomes:**
|
|
||||||
- PostgreSQL → Aurora PostgreSQL
|
|
||||||
- Redis → ElastiCache
|
|
||||||
- Docker Containers → ECS/Fargate or Lambda
|
|
||||||
- RabbitMQ/Redis Pub/Sub → SQS/SNS
|
|
||||||
- Docker Compose → CloudFormation/Terraform
|
|
||||||
- Local networking → VPC, Security Groups
|
|
||||||
|
|
||||||
**Key point:** "The architecture and patterns are production-ready, the infrastructure is local for development convenience"
|
|
||||||
|
|
||||||
## Common Pitfalls to Avoid
|
|
||||||
|
|
||||||
1. **Over-engineering Phase 1** - Resist adding features, just get streaming working
|
|
||||||
2. **Ugly UI** - Don't waste time on design, htmx + basic CSS is fine
|
|
||||||
3. **Perfect metrics** - Mock data is OK early on, real psutil data comes later
|
|
||||||
4. **Complete coverage** - Better to have 3 services working perfectly than 10 half-done
|
|
||||||
5. **AWS deployment** - Local is fine, AWS costs money and adds complexity
|
|
||||||
|
|
||||||
## Success Metrics
|
|
||||||
|
|
||||||
**For Yourself:**
|
|
||||||
- [ ] Actually use the dashboard daily
|
|
||||||
- [ ] Catches a real issue before you notice
|
|
||||||
- [ ] Runs stable for 1+ week without intervention
|
|
||||||
|
|
||||||
**For Interview:**
|
|
||||||
- [ ] Can demo end-to-end in 5 minutes
|
|
||||||
- [ ] Can explain every service interaction
|
|
||||||
- [ ] Can map to payment domain fluently
|
|
||||||
- [ ] Shows understanding of production patterns
|
|
||||||
|
|
||||||
## Next Steps
|
|
||||||
|
|
||||||
1. Set up project structure
|
|
||||||
2. Define proto messages
|
|
||||||
3. Build Phase 1 MVP
|
|
||||||
4. Iterate based on what feels useful
|
|
||||||
5. Polish for demo when interview approaches
|
|
||||||
|
|
||||||
## Resources
|
|
||||||
|
|
||||||
- gRPC Python docs: https://grpc.io/docs/languages/python/
|
|
||||||
- FastAPI WebSockets: https://fastapi.tiangolo.com/advanced/websockets/
|
|
||||||
- TimescaleDB: https://docs.timescale.com/
|
|
||||||
- htmx: https://htmx.org/
|
|
||||||
|
|
||||||
## Questions to Ask Yourself During Development
|
|
||||||
|
|
||||||
- "Would I actually use this feature?"
|
|
||||||
- "How does this map to payments?"
|
|
||||||
- "Can I explain why I built it this way?"
|
|
||||||
- "What would break if X service failed?"
|
|
||||||
- "How would this scale to 1000 machines?"
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Final Note
|
|
||||||
|
|
||||||
This project works because it's:
|
|
||||||
1. **Real** - You'll use it
|
|
||||||
2. **Focused** - Shows specific patterns they care about
|
|
||||||
3. **Mappable** - Clear connection to their domain
|
|
||||||
4. **Yours** - Not a tutorial copy, demonstrates your thinking
|
|
||||||
|
|
||||||
Build it in phases, use it daily, and by interview time you'll have natural stories about trade-offs, failures, and learnings. That authenticity is more valuable than perfect code.
|
|
||||||
|
|
||||||
Good luck! 🚀
|
|
||||||
|
|||||||
214
README.md
Normal file
214
README.md
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
# sysmonstm
|
||||||
|
|
||||||
|
A real-time distributed system monitoring platform that streams metrics from multiple machines to a central hub with a live web dashboard.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
sysmonstm demonstrates production microservices patterns (gRPC streaming, FastAPI, event-driven architecture) while solving a real problem: monitoring development infrastructure across multiple machines.
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||||
|
│ Collector │ │ Collector │ │ Collector │
|
||||||
|
│ (Machine 1) │ │ (Machine 2) │ │ (Machine N) │
|
||||||
|
└────────┬────────┘ └────────┬────────┘ └────────┬────────┘
|
||||||
|
│ │ │
|
||||||
|
│ gRPC Streaming │
|
||||||
|
└───────────────────────┼───────────────────────┘
|
||||||
|
▼
|
||||||
|
┌────────────────────────┐
|
||||||
|
│ Aggregator │
|
||||||
|
│ (gRPC Server + Redis │
|
||||||
|
│ + TimescaleDB) │
|
||||||
|
└────────────┬───────────┘
|
||||||
|
│
|
||||||
|
┌──────────────────┼──────────────────┐
|
||||||
|
│ │ │
|
||||||
|
▼ ▼ ▼
|
||||||
|
┌────────────────┐ ┌──────────────┐ ┌──────────────┐
|
||||||
|
│ Gateway │ │ Alerts │ │ Event Stream│
|
||||||
|
│ (FastAPI + WS) │ │ Service │ │ (Redis PubSub│
|
||||||
|
└────────┬───────┘ └──────────────┘ └──────────────┘
|
||||||
|
│
|
||||||
|
│ WebSocket
|
||||||
|
▼
|
||||||
|
┌────────────────┐
|
||||||
|
│ Browser │
|
||||||
|
│ Dashboard │
|
||||||
|
└────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Real-time streaming**: Collectors stream metrics via gRPC to central aggregator
|
||||||
|
- **Multi-machine support**: Monitor any number of machines from a single dashboard
|
||||||
|
- **Live dashboard**: WebSocket-powered updates with real-time graphs
|
||||||
|
- **Tiered storage**: Redis for hot data, TimescaleDB for historical analysis
|
||||||
|
- **Threshold alerts**: Configurable rules for CPU, memory, disk usage
|
||||||
|
- **Event-driven**: Decoupled services via Redis Pub/Sub
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Start the full stack
|
||||||
|
docker compose up
|
||||||
|
|
||||||
|
# Open dashboard
|
||||||
|
open http://localhost:8000
|
||||||
|
```
|
||||||
|
|
||||||
|
Metrics appear within seconds. The collector runs locally by default.
|
||||||
|
|
||||||
|
### Monitor Additional Machines
|
||||||
|
|
||||||
|
Run the collector on any machine you want to monitor:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# On a remote machine, point to your aggregator
|
||||||
|
COLLECTOR_AGGREGATOR_URL=your-server:50051 \
|
||||||
|
COLLECTOR_MACHINE_ID=my-laptop \
|
||||||
|
python services/collector/main.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Services
|
||||||
|
|
||||||
|
| Service | Port | Description |
|
||||||
|
|---------|------|-------------|
|
||||||
|
| **Collector** | - | gRPC client that streams system metrics (CPU, memory, disk, network) |
|
||||||
|
| **Aggregator** | 50051 | gRPC server that receives metrics, stores them, publishes events |
|
||||||
|
| **Gateway** | 8000 | FastAPI server with REST API and WebSocket for dashboard |
|
||||||
|
| **Alerts** | - | Subscribes to events, evaluates threshold rules, triggers notifications |
|
||||||
|
|
||||||
|
### Infrastructure
|
||||||
|
|
||||||
|
| Component | Purpose |
|
||||||
|
|-----------|---------|
|
||||||
|
| **Redis** | Current state cache, event pub/sub |
|
||||||
|
| **TimescaleDB** | Historical metrics with automatic downsampling |
|
||||||
|
|
||||||
|
### Key Patterns
|
||||||
|
|
||||||
|
- **gRPC Streaming**: Collectors stream metrics continuously to the aggregator
|
||||||
|
- **Event-Driven**: Services communicate via Redis Pub/Sub for decoupling
|
||||||
|
- **Tiered Storage**: Hot data in Redis, historical in TimescaleDB
|
||||||
|
- **Graceful Degradation**: System continues partially if storage fails
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
sysmonstm/
|
||||||
|
├── proto/
|
||||||
|
│ └── metrics.proto # gRPC service definitions
|
||||||
|
├── services/
|
||||||
|
│ ├── collector/ # Metrics collection (psutil)
|
||||||
|
│ ├── aggregator/ # Central gRPC server
|
||||||
|
│ ├── gateway/ # FastAPI + WebSocket
|
||||||
|
│ └── alerts/ # Threshold evaluation
|
||||||
|
├── shared/
|
||||||
|
│ ├── config.py # Pydantic settings
|
||||||
|
│ ├── logging.py # Structured JSON logging
|
||||||
|
│ └── events/ # Event pub/sub abstraction
|
||||||
|
├── web/
|
||||||
|
│ ├── static/ # CSS, JS
|
||||||
|
│ └── templates/ # Dashboard HTML
|
||||||
|
├── scripts/
|
||||||
|
│ └── init-db.sql # TimescaleDB schema
|
||||||
|
├── docs/ # Architecture diagrams & explainers
|
||||||
|
├── docker-compose.yml
|
||||||
|
└── Tiltfile # Local Kubernetes dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
All services use environment variables with sensible defaults:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Collector
|
||||||
|
COLLECTOR_MACHINE_ID=my-machine # Machine identifier
|
||||||
|
COLLECTOR_AGGREGATOR_URL=localhost:50051
|
||||||
|
COLLECTOR_COLLECTION_INTERVAL=5 # Seconds between collections
|
||||||
|
|
||||||
|
# Common
|
||||||
|
REDIS_URL=redis://localhost:6379
|
||||||
|
TIMESCALE_URL=postgresql://monitor:monitor@localhost:5432/monitor
|
||||||
|
LOG_LEVEL=INFO
|
||||||
|
LOG_FORMAT=json
|
||||||
|
```
|
||||||
|
|
||||||
|
## Metrics Collected
|
||||||
|
|
||||||
|
- CPU: Overall percentage, per-core usage
|
||||||
|
- Memory: Percentage, used/available bytes
|
||||||
|
- Disk: Percentage, used bytes, read/write throughput
|
||||||
|
- Network: Bytes sent/received per second, connection count
|
||||||
|
- System: Process count, load averages (1m, 5m, 15m)
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
### Local Development with Hot Reload
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Use the override file for volume mounts
|
||||||
|
docker compose -f docker-compose.yml -f docker-compose.override.yml up
|
||||||
|
```
|
||||||
|
|
||||||
|
### Kubernetes Development with Tilt
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tilt up
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running Services Individually
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install dependencies
|
||||||
|
python -m venv .venv
|
||||||
|
source .venv/bin/activate
|
||||||
|
pip install -r services/collector/requirements.txt
|
||||||
|
|
||||||
|
# Generate protobuf code
|
||||||
|
python -m grpc_tools.protoc -I proto --python_out=. --grpc_python_out=. proto/metrics.proto
|
||||||
|
|
||||||
|
# Run a service
|
||||||
|
python services/collector/main.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Endpoints
|
||||||
|
|
||||||
|
### REST (Gateway)
|
||||||
|
|
||||||
|
| Endpoint | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| `GET /` | Dashboard UI |
|
||||||
|
| `GET /api/machines` | List all monitored machines |
|
||||||
|
| `GET /api/machines/{id}/metrics` | Current metrics for a machine |
|
||||||
|
| `GET /api/machines/{id}/history` | Historical metrics |
|
||||||
|
| `GET /health` | Health check |
|
||||||
|
| `GET /ready` | Readiness check (includes dependencies) |
|
||||||
|
|
||||||
|
### WebSocket
|
||||||
|
|
||||||
|
Connect to `ws://localhost:8000/ws` for real-time metric updates.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
Detailed documentation is available in the `docs/` folder:
|
||||||
|
|
||||||
|
- [Architecture Diagrams](docs/architecture/) - System overview, data flow, deployment
|
||||||
|
- [Building sysmonstm](docs/explainer/sysmonstm-from-start-to-finish.md) - Deep dive into implementation decisions
|
||||||
|
- [Domain Applications](docs/explainer/other-applications.md) - How these patterns apply to payment processing and other domains
|
||||||
|
|
||||||
|
## Tech Stack
|
||||||
|
|
||||||
|
- **Python 3.11+** with async/await throughout
|
||||||
|
- **gRPC** for inter-service communication
|
||||||
|
- **FastAPI** for REST API and WebSocket
|
||||||
|
- **Redis** for caching and pub/sub
|
||||||
|
- **TimescaleDB** for time-series storage
|
||||||
|
- **psutil** for system metrics collection
|
||||||
|
- **Docker Compose** for orchestration
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
3
ctrl/.env.example
Normal file
3
ctrl/.env.example
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
EDGE_URL=wss://sysmonstm.mcrn.ar/ws
|
||||||
|
EDGE_API_KEY=your-api-key-here
|
||||||
|
MACHINE_ID=your-hostname
|
||||||
72
ctrl/README.md
Normal file
72
ctrl/README.md
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
# Deployment Configurations
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────┐ ┌─────────────────────────────────────┐ ┌─────────────┐
|
||||||
|
│ Collector │────▶│ Aggregator + Gateway + Redis + TS │────▶│ Edge │────▶ Browser
|
||||||
|
│ (mcrn) │gRPC │ (LOCAL) │ WS │ (AWS) │ WS
|
||||||
|
└─────────────┘ └─────────────────────────────────────┘ └─────────────┘
|
||||||
|
┌─────────────┐ │
|
||||||
|
│ Collector │────────────────────┘
|
||||||
|
│ (nfrt) │gRPC
|
||||||
|
└─────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Collectors** use gRPC to stream metrics to the local aggregator
|
||||||
|
- **Gateway** forwards to edge via WebSocket (if `EDGE_URL` configured)
|
||||||
|
- **Edge** (AWS) relays to browsers via WebSocket
|
||||||
|
|
||||||
|
## Directory Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
ctrl/
|
||||||
|
├── dev/ # Full stack for local development (docker-compose)
|
||||||
|
└── edge/ # Cloud dashboard for AWS deployment
|
||||||
|
```
|
||||||
|
|
||||||
|
## Local Development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# From repo root
|
||||||
|
docker compose up
|
||||||
|
```
|
||||||
|
|
||||||
|
Runs: aggregator, gateway, collector, alerts, redis, timescaledb
|
||||||
|
|
||||||
|
## Production Deployment
|
||||||
|
|
||||||
|
### 1. Deploy Edge to AWS
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ctrl/edge
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Run Full Stack Locally with Edge Forwarding
|
||||||
|
|
||||||
|
```bash
|
||||||
|
EDGE_URL=wss://sysmonstm.mcrn.ar/ws EDGE_API_KEY=xxx docker compose up
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Run Collectors on Other Machines
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -d --name sysmonstm-collector --network host \
|
||||||
|
-e AGGREGATOR_URL=<local-gateway-ip>:50051 \
|
||||||
|
-e MACHINE_ID=$(hostname) \
|
||||||
|
registry.mcrn.ar/sysmonstm/collector:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
### Gateway (for edge forwarding)
|
||||||
|
- `EDGE_URL` - WebSocket URL of edge (e.g., wss://sysmonstm.mcrn.ar/ws)
|
||||||
|
- `EDGE_API_KEY` - Authentication key for edge
|
||||||
|
|
||||||
|
### Edge
|
||||||
|
- `API_KEY` - Key required from gateway
|
||||||
|
|
||||||
|
### Collector
|
||||||
|
- `AGGREGATOR_URL` - gRPC URL of aggregator (e.g., localhost:50051)
|
||||||
|
- `MACHINE_ID` - Identifier for this machine
|
||||||
46
ctrl/collector.sh
Executable file
46
ctrl/collector.sh
Executable file
@@ -0,0 +1,46 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Run sysmonstm collector only (for secondary machines)
|
||||||
|
# Usage: ./ctrl/collector.sh [aggregator-ip] [--remote]
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# ./ctrl/collector.sh 192.168.1.33 # Build locally (default)
|
||||||
|
# ./ctrl/collector.sh 192.168.1.33 --remote # Use registry image
|
||||||
|
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
AGGREGATOR_IP=${1:-192.168.1.33}
|
||||||
|
MACHINE_ID=$(hostname)
|
||||||
|
USE_REMOTE=false
|
||||||
|
|
||||||
|
if [ "$2" = "--remote" ]; then
|
||||||
|
USE_REMOTE=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if aggregator is reachable
|
||||||
|
echo "Checking connection to aggregator at $AGGREGATOR_IP:50051..."
|
||||||
|
if ! nc -z -w 3 "$AGGREGATOR_IP" 50051 2>/dev/null; then
|
||||||
|
echo ""
|
||||||
|
echo "ERROR: Cannot connect to aggregator at $AGGREGATOR_IP:50051"
|
||||||
|
echo ""
|
||||||
|
echo "Make sure the full stack is running on the main machine first:"
|
||||||
|
echo " cd ~/wdir/sms && ./ctrl/run.sh"
|
||||||
|
echo ""
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Aggregator reachable."
|
||||||
|
|
||||||
|
if [ "$USE_REMOTE" = true ]; then
|
||||||
|
IMAGE="registry.mcrn.ar/sysmonstm/collector:latest"
|
||||||
|
echo "Using remote image: $IMAGE"
|
||||||
|
else
|
||||||
|
IMAGE="sysmonstm-collector:local"
|
||||||
|
echo "Building local image..."
|
||||||
|
docker build -t $IMAGE -f services/collector/Dockerfile .
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Starting collector for $MACHINE_ID -> $AGGREGATOR_IP:50051"
|
||||||
|
|
||||||
|
docker run --rm --name sysmonstm-collector --network host \
|
||||||
|
-e AGGREGATOR_URL=${AGGREGATOR_IP}:50051 \
|
||||||
|
-e MACHINE_ID=${MACHINE_ID} \
|
||||||
|
$IMAGE
|
||||||
156
ctrl/dev/docker-compose.yml
Normal file
156
ctrl/dev/docker-compose.yml
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
version: "3.8"
|
||||||
|
|
||||||
|
# This file works both locally and on EC2 for demo purposes.
|
||||||
|
# For local dev with hot-reload, use: docker compose -f docker-compose.yml -f docker-compose.override.yml up
|
||||||
|
|
||||||
|
x-common-env: &common-env
|
||||||
|
REDIS_URL: redis://redis:6379
|
||||||
|
TIMESCALE_URL: postgresql://monitor:monitor@timescaledb:5432/monitor
|
||||||
|
EVENTS_BACKEND: redis_pubsub
|
||||||
|
LOG_LEVEL: ${LOG_LEVEL:-INFO}
|
||||||
|
LOG_FORMAT: json
|
||||||
|
|
||||||
|
x-healthcheck-defaults: &healthcheck-defaults
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 3
|
||||||
|
start_period: 10s
|
||||||
|
|
||||||
|
services:
|
||||||
|
# =============================================================================
|
||||||
|
# Infrastructure
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:7-alpine
|
||||||
|
ports:
|
||||||
|
- "${REDIS_PORT:-6379}:6379"
|
||||||
|
volumes:
|
||||||
|
- redis-data:/data
|
||||||
|
healthcheck:
|
||||||
|
<<: *healthcheck-defaults
|
||||||
|
test: ["CMD", "redis-cli", "ping"]
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 128M
|
||||||
|
|
||||||
|
timescaledb:
|
||||||
|
image: timescale/timescaledb:latest-pg15
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: monitor
|
||||||
|
POSTGRES_PASSWORD: monitor
|
||||||
|
POSTGRES_DB: monitor
|
||||||
|
ports:
|
||||||
|
- "${TIMESCALE_PORT:-5432}:5432"
|
||||||
|
volumes:
|
||||||
|
- timescale-data:/var/lib/postgresql/data
|
||||||
|
- ./scripts/init-db.sql:/docker-entrypoint-initdb.d/init.sql:ro
|
||||||
|
healthcheck:
|
||||||
|
<<: *healthcheck-defaults
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U monitor -d monitor"]
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 512M
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Application Services
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
aggregator:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: services/aggregator/Dockerfile
|
||||||
|
environment:
|
||||||
|
<<: *common-env
|
||||||
|
GRPC_PORT: 50051
|
||||||
|
SERVICE_NAME: aggregator
|
||||||
|
ports:
|
||||||
|
- "${AGGREGATOR_GRPC_PORT:-50051}:50051"
|
||||||
|
depends_on:
|
||||||
|
redis:
|
||||||
|
condition: service_healthy
|
||||||
|
timescaledb:
|
||||||
|
condition: service_healthy
|
||||||
|
healthcheck:
|
||||||
|
<<: *healthcheck-defaults
|
||||||
|
test: ["CMD", "/bin/grpc_health_probe", "-addr=:50051"]
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 256M
|
||||||
|
|
||||||
|
gateway:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: services/gateway/Dockerfile
|
||||||
|
environment:
|
||||||
|
<<: *common-env
|
||||||
|
HTTP_PORT: 8000
|
||||||
|
AGGREGATOR_URL: aggregator:50051
|
||||||
|
SERVICE_NAME: gateway
|
||||||
|
EDGE_URL: ${EDGE_URL:-}
|
||||||
|
EDGE_API_KEY: ${EDGE_API_KEY:-}
|
||||||
|
ports:
|
||||||
|
- "${GATEWAY_PORT:-8000}:8000"
|
||||||
|
depends_on:
|
||||||
|
- aggregator
|
||||||
|
- redis
|
||||||
|
healthcheck:
|
||||||
|
<<: *healthcheck-defaults
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 256M
|
||||||
|
|
||||||
|
alerts:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: services/alerts/Dockerfile
|
||||||
|
environment:
|
||||||
|
<<: *common-env
|
||||||
|
SERVICE_NAME: alerts
|
||||||
|
depends_on:
|
||||||
|
redis:
|
||||||
|
condition: service_healthy
|
||||||
|
timescaledb:
|
||||||
|
condition: service_healthy
|
||||||
|
healthcheck:
|
||||||
|
<<: *healthcheck-defaults
|
||||||
|
test: ["CMD", "python", "-c", "import sys; sys.exit(0)"]
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 128M
|
||||||
|
|
||||||
|
# Collector runs separately on each machine being monitored
|
||||||
|
# For local testing, we run one instance
|
||||||
|
collector:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: services/collector/Dockerfile
|
||||||
|
environment:
|
||||||
|
<<: *common-env
|
||||||
|
AGGREGATOR_URL: aggregator:50051
|
||||||
|
MACHINE_ID: ${MACHINE_ID:-local-dev}
|
||||||
|
COLLECTION_INTERVAL: ${COLLECTION_INTERVAL:-5}
|
||||||
|
SERVICE_NAME: collector
|
||||||
|
depends_on:
|
||||||
|
- aggregator
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 64M
|
||||||
|
# For actual system metrics, you might need:
|
||||||
|
# privileged: true
|
||||||
|
# pid: host
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
redis-data:
|
||||||
|
timescale-data:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
name: sysmonstm
|
||||||
15
ctrl/edge/Dockerfile
Normal file
15
ctrl/edge/Dockerfile
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN pip install --no-cache-dir fastapi uvicorn[standard] websockets jinja2
|
||||||
|
|
||||||
|
COPY edge.py .
|
||||||
|
COPY templates/ templates/
|
||||||
|
|
||||||
|
ENV API_KEY=""
|
||||||
|
ENV LOG_LEVEL=INFO
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
CMD ["uvicorn", "edge:app", "--host", "0.0.0.0", "--port", "8080"]
|
||||||
15
ctrl/edge/deploy.sh
Executable file
15
ctrl/edge/deploy.sh
Executable file
@@ -0,0 +1,15 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Deploy sysmonstm edge service
|
||||||
|
# Called by Woodpecker or manually
|
||||||
|
|
||||||
|
set -e
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
echo "Pulling latest image..."
|
||||||
|
docker compose pull
|
||||||
|
|
||||||
|
echo "Deploying edge service..."
|
||||||
|
docker compose up -d --remove-orphans
|
||||||
|
|
||||||
|
echo "Deploy complete"
|
||||||
|
docker compose ps
|
||||||
16
ctrl/edge/docker-compose.yml
Normal file
16
ctrl/edge/docker-compose.yml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
services:
|
||||||
|
edge:
|
||||||
|
image: registry.mcrn.ar/sysmonstm/edge:latest
|
||||||
|
container_name: sysmonstm-edge
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
- API_KEY=${API_KEY:-}
|
||||||
|
- LOG_LEVEL=${LOG_LEVEL:-INFO}
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
networks:
|
||||||
|
- gateway
|
||||||
|
|
||||||
|
networks:
|
||||||
|
gateway:
|
||||||
|
external: true
|
||||||
135
ctrl/edge/edge.py
Normal file
135
ctrl/edge/edge.py
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
"""Minimal sysmonstm gateway - standalone mode without dependencies."""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from fastapi import FastAPI, Query, WebSocket, WebSocketDisconnect
|
||||||
|
from fastapi.requests import Request
|
||||||
|
from fastapi.responses import HTMLResponse
|
||||||
|
from fastapi.templating import Jinja2Templates
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
API_KEY = os.environ.get("API_KEY", "")
|
||||||
|
LOG_LEVEL = os.environ.get("LOG_LEVEL", "INFO")
|
||||||
|
|
||||||
|
# Logging setup
|
||||||
|
logging.basicConfig(
|
||||||
|
level=getattr(logging, LOG_LEVEL.upper(), logging.INFO),
|
||||||
|
format="%(asctime)s [%(levelname)s] %(message)s",
|
||||||
|
datefmt="%Y-%m-%d %H:%M:%S",
|
||||||
|
)
|
||||||
|
log = logging.getLogger("gateway")
|
||||||
|
|
||||||
|
app = FastAPI(title="sysmonstm")
|
||||||
|
|
||||||
|
# Templates
|
||||||
|
templates_path = Path(__file__).parent / "templates"
|
||||||
|
templates = Jinja2Templates(directory=str(templates_path))
|
||||||
|
|
||||||
|
# Store connected websockets
|
||||||
|
connections: list[WebSocket] = []
|
||||||
|
# Store latest metrics from collectors
|
||||||
|
machines: dict = {}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/", response_class=HTMLResponse)
|
||||||
|
async def index(request: Request):
|
||||||
|
return templates.TemplateResponse("dashboard.html", {"request": request})
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/health")
|
||||||
|
async def health():
|
||||||
|
return {"status": "ok", "machines": len(machines)}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/api/machines")
|
||||||
|
async def get_machines():
|
||||||
|
return machines
|
||||||
|
|
||||||
|
|
||||||
|
@app.websocket("/ws")
|
||||||
|
async def websocket_endpoint(websocket: WebSocket, key: str = Query(default="")):
|
||||||
|
# API key validation for collectors (browsers don't need key)
|
||||||
|
# We validate key only when metrics are received, allowing browsers to connect freely
|
||||||
|
|
||||||
|
await websocket.accept()
|
||||||
|
connections.append(websocket)
|
||||||
|
client = websocket.client.host if websocket.client else "unknown"
|
||||||
|
log.info(f"WebSocket connected: {client}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Send current state to new connection
|
||||||
|
for machine_id, data in machines.items():
|
||||||
|
await websocket.send_json(
|
||||||
|
{"type": "metrics", "machine_id": machine_id, **data}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Main loop
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
msg = await asyncio.wait_for(websocket.receive_text(), timeout=30)
|
||||||
|
data = json.loads(msg)
|
||||||
|
|
||||||
|
if data.get("type") == "metrics":
|
||||||
|
# Validate API key for metric submissions
|
||||||
|
if API_KEY and key != API_KEY:
|
||||||
|
log.warning(f"Invalid API key from {client}")
|
||||||
|
await websocket.close(code=4001, reason="Invalid API key")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Handle both formats:
|
||||||
|
# 1. Direct: {"type": "metrics", "machine_id": "...", "cpu": ...}
|
||||||
|
# 2. Nested (from gateway): {"type": "metrics", "data": {...}, "timestamp": "..."}
|
||||||
|
if "data" in data and isinstance(data["data"], dict):
|
||||||
|
# Nested format from gateway forwarding
|
||||||
|
payload = data["data"]
|
||||||
|
machine_id = payload.get("machine_id", "unknown")
|
||||||
|
# Extract metrics from nested structure
|
||||||
|
metrics = payload.get("metrics", {})
|
||||||
|
metric_data = {
|
||||||
|
"type": "metrics",
|
||||||
|
"machine_id": machine_id,
|
||||||
|
"hostname": payload.get("hostname", ""),
|
||||||
|
"timestamp": data.get("timestamp"),
|
||||||
|
}
|
||||||
|
# Flatten metrics for dashboard display
|
||||||
|
for key_name, value in metrics.items():
|
||||||
|
metric_data[key_name.lower()] = value
|
||||||
|
machines[machine_id] = metric_data
|
||||||
|
log.debug(f"Metrics (forwarded) from {machine_id}")
|
||||||
|
else:
|
||||||
|
# Direct format from collector
|
||||||
|
machine_id = data.get("machine_id", "unknown")
|
||||||
|
machines[machine_id] = data
|
||||||
|
log.debug(f"Metrics from {machine_id}: cpu={data.get('cpu')}%")
|
||||||
|
|
||||||
|
# Broadcast to all connected clients
|
||||||
|
broadcast_data = machines[machine_id]
|
||||||
|
for conn in connections:
|
||||||
|
try:
|
||||||
|
await conn.send_json(broadcast_data)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
# Send ping to keep connection alive
|
||||||
|
await websocket.send_json({"type": "ping"})
|
||||||
|
|
||||||
|
except WebSocketDisconnect:
|
||||||
|
log.info(f"WebSocket disconnected: {client}")
|
||||||
|
except Exception as e:
|
||||||
|
log.error(f"WebSocket error: {e}")
|
||||||
|
finally:
|
||||||
|
if websocket in connections:
|
||||||
|
connections.remove(websocket)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import uvicorn
|
||||||
|
|
||||||
|
log.info("Starting sysmonstm gateway")
|
||||||
|
log.info(f" API key: {'configured' if API_KEY else 'not set (open)'}")
|
||||||
|
uvicorn.run(app, host="0.0.0.0", port=8080)
|
||||||
349
ctrl/edge/templates/dashboard.html
Normal file
349
ctrl/edge/templates/dashboard.html
Normal file
@@ -0,0 +1,349 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>System Monitor Dashboard</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--bg-primary: #1a1a2e;
|
||||||
|
--bg-secondary: #16213e;
|
||||||
|
--bg-card: #0f3460;
|
||||||
|
--text-primary: #eee;
|
||||||
|
--text-secondary: #a0a0a0;
|
||||||
|
--accent: #e94560;
|
||||||
|
--success: #4ade80;
|
||||||
|
--warning: #fbbf24;
|
||||||
|
--danger: #ef4444;
|
||||||
|
--border: #2a2a4a;
|
||||||
|
}
|
||||||
|
|
||||||
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: system-ui, -apple-system, sans-serif;
|
||||||
|
background: var(--bg-primary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
padding: 1rem 2rem;
|
||||||
|
border-bottom: 2px solid var(--accent);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
header h1 { font-size: 1.5rem; }
|
||||||
|
|
||||||
|
.status {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-dot {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-dot.connected { background: var(--success); }
|
||||||
|
|
||||||
|
main {
|
||||||
|
padding: 1.5rem;
|
||||||
|
max-width: 1600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.machines-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.machine-card {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1.25rem;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.machine-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
padding-bottom: 0.75rem;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.machine-name {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.machine-id {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.machine-status {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: var(--success);
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.machine-status.warning { background: var(--warning); }
|
||||||
|
.machine-status.critical { background: var(--danger); color: #fff; }
|
||||||
|
|
||||||
|
.metrics-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric {
|
||||||
|
background: var(--bg-card);
|
||||||
|
padding: 0.75rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-label {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-value {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-bar {
|
||||||
|
height: 4px;
|
||||||
|
background: var(--border);
|
||||||
|
border-radius: 2px;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-bar-fill {
|
||||||
|
height: 100%;
|
||||||
|
background: var(--success);
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-bar-fill.warning { background: var(--warning); }
|
||||||
|
.metric-bar-fill.critical { background: var(--danger); }
|
||||||
|
|
||||||
|
.last-seen {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-top: 1rem;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-machines {
|
||||||
|
text-align: center;
|
||||||
|
padding: 3rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-machines h2 {
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.machines-grid { grid-template-columns: 1fr; }
|
||||||
|
.metrics-grid { grid-template-columns: 1fr; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<h1>System Monitor</h1>
|
||||||
|
<div class="status">
|
||||||
|
<span class="status-dot" id="status-dot"></span>
|
||||||
|
<span id="status-text">Connecting...</span>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<div class="machines-grid" id="machines-grid">
|
||||||
|
<div class="no-machines">
|
||||||
|
<h2>No machines connected</h2>
|
||||||
|
<p>Waiting for collectors to send metrics...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const machinesGrid = document.getElementById('machines-grid');
|
||||||
|
const statusDot = document.getElementById('status-dot');
|
||||||
|
const statusText = document.getElementById('status-text');
|
||||||
|
|
||||||
|
const machines = new Map();
|
||||||
|
|
||||||
|
function formatBytes(bytes) {
|
||||||
|
if (bytes === 0) return '0 B';
|
||||||
|
const k = 1024;
|
||||||
|
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatRate(bytesPerSec) {
|
||||||
|
return formatBytes(bytesPerSec) + '/s';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBarClass(value, warning = 80, critical = 95) {
|
||||||
|
if (value >= critical) return 'critical';
|
||||||
|
if (value >= warning) return 'warning';
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStatusClass(m) {
|
||||||
|
const cpu = m.cpu_percent || 0;
|
||||||
|
const mem = m.memory_percent || 0;
|
||||||
|
const disk = m.disk_percent || 0;
|
||||||
|
|
||||||
|
if (cpu > 95 || mem > 95 || disk > 90) return 'critical';
|
||||||
|
if (cpu > 80 || mem > 85 || disk > 80) return 'warning';
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function timeSince(timestamp) {
|
||||||
|
if (!timestamp) return '-';
|
||||||
|
const date = typeof timestamp === 'string' ? new Date(timestamp) : new Date(timestamp);
|
||||||
|
const seconds = Math.floor((Date.now() - date.getTime()) / 1000);
|
||||||
|
if (seconds < 5) return 'just now';
|
||||||
|
if (seconds < 60) return seconds + 's ago';
|
||||||
|
const minutes = Math.floor(seconds / 60);
|
||||||
|
if (minutes < 60) return minutes + 'm ago';
|
||||||
|
return Math.floor(minutes / 60) + 'h ago';
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderMachine(data) {
|
||||||
|
const m = data;
|
||||||
|
const statusClass = getStatusClass(m);
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="machine-card" data-machine="${data.machine_id}">
|
||||||
|
<div class="machine-header">
|
||||||
|
<div>
|
||||||
|
<div class="machine-name">${data.hostname || data.machine_id}</div>
|
||||||
|
<div class="machine-id">${data.machine_id}</div>
|
||||||
|
</div>
|
||||||
|
<span class="machine-status ${statusClass}">${statusClass || 'healthy'}</span>
|
||||||
|
</div>
|
||||||
|
<div class="metrics-grid">
|
||||||
|
<div class="metric">
|
||||||
|
<div class="metric-label">CPU</div>
|
||||||
|
<div class="metric-value">${(m.cpu_percent || 0).toFixed(1)}%</div>
|
||||||
|
<div class="metric-bar">
|
||||||
|
<div class="metric-bar-fill ${getBarClass(m.cpu_percent || 0)}"
|
||||||
|
style="width: ${m.cpu_percent || 0}%"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="metric">
|
||||||
|
<div class="metric-label">Memory</div>
|
||||||
|
<div class="metric-value">${(m.memory_percent || 0).toFixed(1)}%</div>
|
||||||
|
<div class="metric-bar">
|
||||||
|
<div class="metric-bar-fill ${getBarClass(m.memory_percent || 0, 85, 95)}"
|
||||||
|
style="width: ${m.memory_percent || 0}%"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="metric">
|
||||||
|
<div class="metric-label">Disk</div>
|
||||||
|
<div class="metric-value">${(m.disk_percent || 0).toFixed(1)}%</div>
|
||||||
|
<div class="metric-bar">
|
||||||
|
<div class="metric-bar-fill ${getBarClass(m.disk_percent || 0, 80, 90)}"
|
||||||
|
style="width: ${m.disk_percent || 0}%"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="metric">
|
||||||
|
<div class="metric-label">Load (1m)</div>
|
||||||
|
<div class="metric-value">${(m.load_avg_1m || 0).toFixed(2)}</div>
|
||||||
|
</div>
|
||||||
|
<div class="metric">
|
||||||
|
<div class="metric-label">Network In</div>
|
||||||
|
<div class="metric-value">${formatRate(m.network_recv_bytes_sec || 0)}</div>
|
||||||
|
</div>
|
||||||
|
<div class="metric">
|
||||||
|
<div class="metric-label">Network Out</div>
|
||||||
|
<div class="metric-value">${formatRate(m.network_sent_bytes_sec || 0)}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="last-seen">Last seen: ${timeSince(m.timestamp)}</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateUI() {
|
||||||
|
if (machines.size === 0) {
|
||||||
|
machinesGrid.innerHTML = `
|
||||||
|
<div class="no-machines">
|
||||||
|
<h2>No machines connected</h2>
|
||||||
|
<p>Waiting for collectors to send metrics...</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
machinesGrid.innerHTML = Array.from(machines.values())
|
||||||
|
.map(renderMachine)
|
||||||
|
.join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
function connect() {
|
||||||
|
const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||||
|
const ws = new WebSocket(`${protocol}//${location.host}/ws`);
|
||||||
|
|
||||||
|
ws.onopen = () => {
|
||||||
|
statusDot.classList.add('connected');
|
||||||
|
statusText.textContent = 'Connected';
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onclose = () => {
|
||||||
|
statusDot.classList.remove('connected');
|
||||||
|
statusText.textContent = 'Disconnected - Reconnecting...';
|
||||||
|
setTimeout(connect, 3000);
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onerror = () => {
|
||||||
|
statusDot.classList.remove('connected');
|
||||||
|
statusText.textContent = 'Connection error';
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onmessage = (event) => {
|
||||||
|
try {
|
||||||
|
const msg = JSON.parse(event.data);
|
||||||
|
if (msg.type === 'metrics' || msg.type === 'initial') {
|
||||||
|
machines.set(msg.machine_id, msg);
|
||||||
|
updateUI();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to parse message:', e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
if (ws.readyState === WebSocket.OPEN) {
|
||||||
|
ws.send('ping');
|
||||||
|
}
|
||||||
|
}, 30000);
|
||||||
|
}
|
||||||
|
|
||||||
|
setInterval(updateUI, 5000);
|
||||||
|
connect();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
23
ctrl/run.sh
Executable file
23
ctrl/run.sh
Executable file
@@ -0,0 +1,23 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Run sysmonstm full stack locally with edge forwarding
|
||||||
|
# Usage: ./ctrl/run.sh [--remote]
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# ./ctrl/run.sh # Build locally (default)
|
||||||
|
# ./ctrl/run.sh --remote # Use registry images
|
||||||
|
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
# Load env from ctrl/.env
|
||||||
|
set -a
|
||||||
|
source ctrl/.env
|
||||||
|
set +a
|
||||||
|
|
||||||
|
if [ "$1" = "--remote" ]; then
|
||||||
|
echo "Using remote images from registry"
|
||||||
|
docker compose pull
|
||||||
|
docker compose up "${@:2}"
|
||||||
|
else
|
||||||
|
echo "Building locally..."
|
||||||
|
docker compose up --build "$@"
|
||||||
|
fi
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
FROM python:3.11-slim
|
|
||||||
WORKDIR /app
|
|
||||||
RUN pip install --no-cache-dir fastapi uvicorn[standard] websockets
|
|
||||||
COPY main.py .
|
|
||||||
EXPOSE 8080
|
|
||||||
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8080"]
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
services:
|
|
||||||
sysmonstm:
|
|
||||||
build: .
|
|
||||||
container_name: sysmonstm
|
|
||||||
restart: unless-stopped
|
|
||||||
ports:
|
|
||||||
- "8080:8080"
|
|
||||||
networks:
|
|
||||||
- gateway
|
|
||||||
|
|
||||||
networks:
|
|
||||||
gateway:
|
|
||||||
external: true
|
|
||||||
@@ -1,198 +0,0 @@
|
|||||||
"""Minimal sysmonstm gateway - standalone mode without dependencies."""
|
|
||||||
|
|
||||||
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
|
|
||||||
from fastapi.responses import HTMLResponse
|
|
||||||
import json
|
|
||||||
import asyncio
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
app = FastAPI(title="sysmonstm")
|
|
||||||
|
|
||||||
# Store connected websockets
|
|
||||||
connections: list[WebSocket] = []
|
|
||||||
# Store latest metrics from collectors
|
|
||||||
machines: dict = {}
|
|
||||||
|
|
||||||
HTML = """
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>sysmonstm</title>
|
|
||||||
<style>
|
|
||||||
:root {
|
|
||||||
--bg: #1a1a2e;
|
|
||||||
--bg2: #16213e;
|
|
||||||
--text: #eee;
|
|
||||||
--accent: #e94560;
|
|
||||||
--success: #4ade80;
|
|
||||||
--muted: #666;
|
|
||||||
}
|
|
||||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
||||||
body {
|
|
||||||
font-family: system-ui, sans-serif;
|
|
||||||
background: var(--bg);
|
|
||||||
color: var(--text);
|
|
||||||
min-height: 100vh;
|
|
||||||
padding: 2rem;
|
|
||||||
}
|
|
||||||
header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
padding-bottom: 1rem;
|
|
||||||
border-bottom: 2px solid var(--accent);
|
|
||||||
}
|
|
||||||
h1 { font-size: 1.5rem; }
|
|
||||||
.status {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
}
|
|
||||||
.dot {
|
|
||||||
width: 10px;
|
|
||||||
height: 10px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: var(--accent);
|
|
||||||
}
|
|
||||||
.dot.ok { background: var(--success); }
|
|
||||||
.machines {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
.machine {
|
|
||||||
background: var(--bg2);
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
.machine h3 { margin-bottom: 0.5rem; }
|
|
||||||
.metric {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 0.25rem 0;
|
|
||||||
border-bottom: 1px solid #333;
|
|
||||||
}
|
|
||||||
.empty {
|
|
||||||
text-align: center;
|
|
||||||
color: var(--muted);
|
|
||||||
padding: 4rem;
|
|
||||||
}
|
|
||||||
.empty p { margin-top: 1rem; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
<h1>sysmonstm</h1>
|
|
||||||
<div class="status">
|
|
||||||
<span class="dot" id="ws-status"></span>
|
|
||||||
<span id="status-text">connecting...</span>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<main>
|
|
||||||
<div id="machines" class="machines">
|
|
||||||
<div class="empty">
|
|
||||||
<h2>No collectors connected</h2>
|
|
||||||
<p>Start a collector to see metrics</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
<script>
|
|
||||||
const machinesEl = document.getElementById('machines');
|
|
||||||
const statusDot = document.getElementById('ws-status');
|
|
||||||
const statusText = document.getElementById('status-text');
|
|
||||||
let machines = {};
|
|
||||||
|
|
||||||
function connect() {
|
|
||||||
const ws = new WebSocket(`wss://${location.host}/ws`);
|
|
||||||
|
|
||||||
ws.onopen = () => {
|
|
||||||
statusDot.classList.add('ok');
|
|
||||||
statusText.textContent = 'connected';
|
|
||||||
};
|
|
||||||
|
|
||||||
ws.onclose = () => {
|
|
||||||
statusDot.classList.remove('ok');
|
|
||||||
statusText.textContent = 'disconnected';
|
|
||||||
setTimeout(connect, 2000);
|
|
||||||
};
|
|
||||||
|
|
||||||
ws.onmessage = (e) => {
|
|
||||||
const data = JSON.parse(e.data);
|
|
||||||
if (data.type === 'metrics') {
|
|
||||||
machines[data.machine_id] = data;
|
|
||||||
render();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function render() {
|
|
||||||
const ids = Object.keys(machines);
|
|
||||||
if (ids.length === 0) {
|
|
||||||
machinesEl.innerHTML = '<div class="empty"><h2>No collectors connected</h2><p>Start a collector to see metrics</p></div>';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
machinesEl.innerHTML = ids.map(id => {
|
|
||||||
const m = machines[id];
|
|
||||||
return `
|
|
||||||
<div class="machine">
|
|
||||||
<h3>${id}</h3>
|
|
||||||
<div class="metric"><span>CPU</span><span>${m.cpu?.toFixed(1) || '-'}%</span></div>
|
|
||||||
<div class="metric"><span>Memory</span><span>${m.memory?.toFixed(1) || '-'}%</span></div>
|
|
||||||
<div class="metric"><span>Disk</span><span>${m.disk?.toFixed(1) || '-'}%</span></div>
|
|
||||||
<div class="metric"><span>Updated</span><span>${new Date(m.timestamp).toLocaleTimeString()}</span></div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}).join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
connect();
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
"""
|
|
||||||
|
|
||||||
@app.get("/", response_class=HTMLResponse)
|
|
||||||
async def index():
|
|
||||||
return HTML
|
|
||||||
|
|
||||||
@app.get("/health")
|
|
||||||
async def health():
|
|
||||||
return {"status": "ok", "machines": len(machines)}
|
|
||||||
|
|
||||||
@app.websocket("/ws")
|
|
||||||
async def websocket_endpoint(websocket: WebSocket):
|
|
||||||
await websocket.accept()
|
|
||||||
connections.append(websocket)
|
|
||||||
try:
|
|
||||||
# Send current state
|
|
||||||
for machine_id, data in machines.items():
|
|
||||||
await websocket.send_json({"type": "metrics", "machine_id": machine_id, **data})
|
|
||||||
# Keep alive
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
msg = await asyncio.wait_for(websocket.receive_text(), timeout=30)
|
|
||||||
data = json.loads(msg)
|
|
||||||
if data.get("type") == "metrics":
|
|
||||||
machine_id = data.get("machine_id", "unknown")
|
|
||||||
machines[machine_id] = {**data, "timestamp": datetime.utcnow().isoformat()}
|
|
||||||
# Broadcast to all
|
|
||||||
for conn in connections:
|
|
||||||
try:
|
|
||||||
await conn.send_json({"type": "metrics", "machine_id": machine_id, **machines[machine_id]})
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
except asyncio.TimeoutError:
|
|
||||||
await websocket.send_json({"type": "ping"})
|
|
||||||
except WebSocketDisconnect:
|
|
||||||
pass
|
|
||||||
finally:
|
|
||||||
connections.remove(websocket)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import uvicorn
|
|
||||||
uvicorn.run(app, host="0.0.0.0", port=8080)
|
|
||||||
@@ -1,154 +0,0 @@
|
|||||||
version: "3.8"
|
|
||||||
|
|
||||||
# This file works both locally and on EC2 for demo purposes.
|
|
||||||
# For local dev with hot-reload, use: docker compose -f docker-compose.yml -f docker-compose.override.yml up
|
|
||||||
|
|
||||||
x-common-env: &common-env
|
|
||||||
REDIS_URL: redis://redis:6379
|
|
||||||
TIMESCALE_URL: postgresql://monitor:monitor@timescaledb:5432/monitor
|
|
||||||
EVENTS_BACKEND: redis_pubsub
|
|
||||||
LOG_LEVEL: ${LOG_LEVEL:-INFO}
|
|
||||||
LOG_FORMAT: json
|
|
||||||
|
|
||||||
x-healthcheck-defaults: &healthcheck-defaults
|
|
||||||
interval: 10s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 3
|
|
||||||
start_period: 10s
|
|
||||||
|
|
||||||
services:
|
|
||||||
# =============================================================================
|
|
||||||
# Infrastructure
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
redis:
|
|
||||||
image: redis:7-alpine
|
|
||||||
ports:
|
|
||||||
- "${REDIS_PORT:-6379}:6379"
|
|
||||||
volumes:
|
|
||||||
- redis-data:/data
|
|
||||||
healthcheck:
|
|
||||||
<<: *healthcheck-defaults
|
|
||||||
test: ["CMD", "redis-cli", "ping"]
|
|
||||||
deploy:
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
memory: 128M
|
|
||||||
|
|
||||||
timescaledb:
|
|
||||||
image: timescale/timescaledb:latest-pg15
|
|
||||||
environment:
|
|
||||||
POSTGRES_USER: monitor
|
|
||||||
POSTGRES_PASSWORD: monitor
|
|
||||||
POSTGRES_DB: monitor
|
|
||||||
ports:
|
|
||||||
- "${TIMESCALE_PORT:-5432}:5432"
|
|
||||||
volumes:
|
|
||||||
- timescale-data:/var/lib/postgresql/data
|
|
||||||
- ./scripts/init-db.sql:/docker-entrypoint-initdb.d/init.sql:ro
|
|
||||||
healthcheck:
|
|
||||||
<<: *healthcheck-defaults
|
|
||||||
test: ["CMD-SHELL", "pg_isready -U monitor -d monitor"]
|
|
||||||
deploy:
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
memory: 512M
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# Application Services
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
aggregator:
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
dockerfile: services/aggregator/Dockerfile
|
|
||||||
environment:
|
|
||||||
<<: *common-env
|
|
||||||
GRPC_PORT: 50051
|
|
||||||
SERVICE_NAME: aggregator
|
|
||||||
ports:
|
|
||||||
- "${AGGREGATOR_GRPC_PORT:-50051}:50051"
|
|
||||||
depends_on:
|
|
||||||
redis:
|
|
||||||
condition: service_healthy
|
|
||||||
timescaledb:
|
|
||||||
condition: service_healthy
|
|
||||||
healthcheck:
|
|
||||||
<<: *healthcheck-defaults
|
|
||||||
test: ["CMD", "/bin/grpc_health_probe", "-addr=:50051"]
|
|
||||||
deploy:
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
memory: 256M
|
|
||||||
|
|
||||||
gateway:
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
dockerfile: services/gateway/Dockerfile
|
|
||||||
environment:
|
|
||||||
<<: *common-env
|
|
||||||
HTTP_PORT: 8000
|
|
||||||
AGGREGATOR_URL: aggregator:50051
|
|
||||||
SERVICE_NAME: gateway
|
|
||||||
ports:
|
|
||||||
- "${GATEWAY_PORT:-8000}:8000"
|
|
||||||
depends_on:
|
|
||||||
- aggregator
|
|
||||||
- redis
|
|
||||||
healthcheck:
|
|
||||||
<<: *healthcheck-defaults
|
|
||||||
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
|
|
||||||
deploy:
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
memory: 256M
|
|
||||||
|
|
||||||
alerts:
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
dockerfile: services/alerts/Dockerfile
|
|
||||||
environment:
|
|
||||||
<<: *common-env
|
|
||||||
SERVICE_NAME: alerts
|
|
||||||
depends_on:
|
|
||||||
redis:
|
|
||||||
condition: service_healthy
|
|
||||||
timescaledb:
|
|
||||||
condition: service_healthy
|
|
||||||
healthcheck:
|
|
||||||
<<: *healthcheck-defaults
|
|
||||||
test: ["CMD", "python", "-c", "import sys; sys.exit(0)"]
|
|
||||||
deploy:
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
memory: 128M
|
|
||||||
|
|
||||||
# Collector runs separately on each machine being monitored
|
|
||||||
# For local testing, we run one instance
|
|
||||||
collector:
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
dockerfile: services/collector/Dockerfile
|
|
||||||
environment:
|
|
||||||
<<: *common-env
|
|
||||||
AGGREGATOR_URL: aggregator:50051
|
|
||||||
MACHINE_ID: ${MACHINE_ID:-local-dev}
|
|
||||||
COLLECTION_INTERVAL: ${COLLECTION_INTERVAL:-5}
|
|
||||||
SERVICE_NAME: collector
|
|
||||||
depends_on:
|
|
||||||
- aggregator
|
|
||||||
deploy:
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
memory: 64M
|
|
||||||
# For actual system metrics, you might need:
|
|
||||||
# privileged: true
|
|
||||||
# pid: host
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
redis-data:
|
|
||||||
timescale-data:
|
|
||||||
|
|
||||||
networks:
|
|
||||||
default:
|
|
||||||
name: sysmonstm
|
|
||||||
1
docker-compose.yml
Symbolic link
1
docker-compose.yml
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
ctrl/dev/docker-compose.yml
|
||||||
@@ -24,9 +24,19 @@ digraph SystemOverview {
|
|||||||
machines [label="Monitored\nMachines", fillcolor="#FFF3E0", shape=box3d];
|
machines [label="Monitored\nMachines", fillcolor="#FFF3E0", shape=box3d];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Edge (AWS)
|
||||||
|
subgraph cluster_edge {
|
||||||
|
label="AWS (sysmonstm.mcrn.ar)";
|
||||||
|
style=filled;
|
||||||
|
color="#F3E5F5";
|
||||||
|
fillcolor="#F3E5F5";
|
||||||
|
|
||||||
|
edge_relay [label="Edge\n(WebSocket Relay)", fillcolor="#E1BEE7"];
|
||||||
|
}
|
||||||
|
|
||||||
// Core Services
|
// Core Services
|
||||||
subgraph cluster_services {
|
subgraph cluster_services {
|
||||||
label="Application Services";
|
label="Local Stack";
|
||||||
style=filled;
|
style=filled;
|
||||||
color="#E8F5E9";
|
color="#E8F5E9";
|
||||||
fillcolor="#E8F5E9";
|
fillcolor="#E8F5E9";
|
||||||
@@ -59,7 +69,10 @@ digraph SystemOverview {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Connections
|
// Connections
|
||||||
browser -> gateway [label="WebSocket\nREST", color="#1976D2"];
|
browser -> edge_relay [label="WebSocket", color="#1976D2"];
|
||||||
|
edge_relay -> gateway [label="WebSocket\nForward", color="#1976D2", dir=back];
|
||||||
|
|
||||||
|
browser -> gateway [label="WebSocket\n(local dev)", color="#1976D2", style=dashed];
|
||||||
gateway -> aggregator [label="gRPC", color="#388E3C"];
|
gateway -> aggregator [label="gRPC", color="#388E3C"];
|
||||||
gateway -> redis [label="State\nQuery", style=dashed];
|
gateway -> redis [label="State\nQuery", style=dashed];
|
||||||
gateway -> timescale [label="Historical\nQuery", style=dashed];
|
gateway -> timescale [label="Historical\nQuery", style=dashed];
|
||||||
@@ -73,6 +86,4 @@ digraph SystemOverview {
|
|||||||
|
|
||||||
events -> alerts [label="Subscribe", color="#7B1FA2"];
|
events -> alerts [label="Subscribe", color="#7B1FA2"];
|
||||||
events -> gateway [label="Subscribe", color="#7B1FA2"];
|
events -> gateway [label="Subscribe", color="#7B1FA2"];
|
||||||
|
|
||||||
alerts -> timescale [label="Store\nAlerts", style=dashed];
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,193 +1,212 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
<!-- Generated by graphviz version 14.1.1 (0)
|
<!-- Generated by graphviz version 14.1.2 (0)
|
||||||
-->
|
-->
|
||||||
<!-- Title: SystemOverview Pages: 1 -->
|
<!-- Title: SystemOverview Pages: 1 -->
|
||||||
<svg width="444pt" height="508pt"
|
<svg width="577pt" height="618pt"
|
||||||
viewBox="0.00 0.00 444.00 508.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
viewBox="0.00 0.00 577.00 618.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)">
|
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 614.03)">
|
||||||
<title>SystemOverview</title>
|
<title>SystemOverview</title>
|
||||||
<polygon fill="white" stroke="none" points="-4,4 -4,-503.78 440,-503.78 440,4 -4,4"/>
|
<polygon fill="white" stroke="none" points="-4,4 -4,-614.03 573,-614.03 573,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>
|
<text xml:space="preserve" text-anchor="middle" x="284.5" y="-590.83" font-family="Helvetica,sans-Serif" font-size="16.00">System Monitoring Platform - Architecture Overview</text>
|
||||||
<g id="clust1" class="cluster">
|
<g id="clust1" class="cluster">
|
||||||
<title>cluster_external</title>
|
<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"/>
|
<polygon fill="none" stroke="gray" stroke-dasharray="5,2" points="208,-495.03 208,-574.53 398,-574.53 398,-495.03 208,-495.03"/>
|
||||||
<text xml:space="preserve" text-anchor="middle" x="140.5" y="-434.5" font-family="Helvetica,sans-Serif" font-size="16.00">External</text>
|
<text xml:space="preserve" text-anchor="middle" x="303" y="-555.33" font-family="Helvetica,sans-Serif" font-size="16.00">External</text>
|
||||||
</g>
|
</g>
|
||||||
<g id="clust2" class="cluster">
|
<g id="clust2" class="cluster">
|
||||||
<title>cluster_services</title>
|
<title>cluster_edge</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"/>
|
<polygon fill="#f3e5f5" stroke="#f3e5f5" points="8,-374.2 8,-453.7 238,-453.7 238,-374.2 8,-374.2"/>
|
||||||
<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>
|
<text xml:space="preserve" text-anchor="middle" x="123" y="-434.5" font-family="Helvetica,sans-Serif" font-size="16.00">AWS (sysmonstm.mcrn.ar)</text>
|
||||||
</g>
|
</g>
|
||||||
<g id="clust3" class="cluster">
|
<g id="clust3" class="cluster">
|
||||||
<title>cluster_data</title>
|
<title>cluster_services</title>
|
||||||
<polygon fill="#fff8e1" stroke="#fff8e1" points="22.5,-8 22.5,-99.62 260.5,-99.62 260.5,-8 22.5,-8"/>
|
<polygon fill="#e8f5e9" stroke="#e8f5e9" points="227,-143.12 227,-320.12 489,-320.12 489,-143.12 227,-143.12"/>
|
||||||
<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>
|
<text xml:space="preserve" text-anchor="middle" x="358" y="-300.93" font-family="Helvetica,sans-Serif" font-size="16.00">Local Stack</text>
|
||||||
</g>
|
</g>
|
||||||
<g id="clust4" class="cluster">
|
<g id="clust4" class="cluster">
|
||||||
|
<title>cluster_data</title>
|
||||||
|
<polygon fill="#fff8e1" stroke="#fff8e1" points="162,-8 162,-99.62 400,-99.62 400,-8 162,-8"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="281" y="-80.42" font-family="Helvetica,sans-Serif" font-size="16.00">Data Layer</text>
|
||||||
|
</g>
|
||||||
|
<g id="clust5" class="cluster">
|
||||||
<title>cluster_events</title>
|
<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"/>
|
<polygon fill="#f3e5f5" stroke="#f3e5f5" points="391,-363.62 391,-464.28 561,-464.28 561,-363.62 391,-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>
|
<text xml:space="preserve" text-anchor="middle" x="476" y="-445.08" font-family="Helvetica,sans-Serif" font-size="16.00">Event Stream</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- browser -->
|
<!-- browser -->
|
||||||
<g id="node1" class="node">
|
<g id="node1" class="node">
|
||||||
<title>browser</title>
|
<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"/>
|
<path fill="#e3f2fd" stroke="black" d="M288.12,-539.03C288.12,-539.03 227.88,-539.03 227.88,-539.03 221.88,-539.03 215.88,-533.03 215.88,-527.03 215.88,-527.03 215.88,-515.03 215.88,-515.03 215.88,-509.03 221.88,-503.03 227.88,-503.03 227.88,-503.03 288.12,-503.03 288.12,-503.03 294.12,-503.03 300.12,-509.03 300.12,-515.03 300.12,-515.03 300.12,-527.03 300.12,-527.03 300.12,-533.03 294.12,-539.03 288.12,-539.03"/>
|
||||||
<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="258" y="-524.08" 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>
|
<text xml:space="preserve" text-anchor="middle" x="258" y="-510.58" font-family="Helvetica,sans-Serif" font-size="11.00">(Dashboard)</text>
|
||||||
|
</g>
|
||||||
|
<!-- edge_relay -->
|
||||||
|
<g id="node3" class="node">
|
||||||
|
<title>edge_relay</title>
|
||||||
|
<path fill="#e1bee7" stroke="black" d="M217.12,-418.2C217.12,-418.2 120.88,-418.2 120.88,-418.2 114.88,-418.2 108.88,-412.2 108.88,-406.2 108.88,-406.2 108.88,-394.2 108.88,-394.2 108.88,-388.2 114.88,-382.2 120.88,-382.2 120.88,-382.2 217.12,-382.2 217.12,-382.2 223.12,-382.2 229.12,-388.2 229.12,-394.2 229.12,-394.2 229.12,-406.2 229.12,-406.2 229.12,-412.2 223.12,-418.2 217.12,-418.2"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="169" y="-403.25" font-family="Helvetica,sans-Serif" font-size="11.00">Edge</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="169" y="-389.75" font-family="Helvetica,sans-Serif" font-size="11.00">(WebSocket Relay)</text>
|
||||||
|
</g>
|
||||||
|
<!-- browser->edge_relay -->
|
||||||
|
<g id="edge1" class="edge">
|
||||||
|
<title>browser->edge_relay</title>
|
||||||
|
<path fill="none" stroke="#1976d2" d="M218.56,-502.6C210.86,-497.79 203.43,-491.95 197.75,-485.03 184.82,-469.28 177.57,-447.39 173.59,-429.93"/>
|
||||||
|
<polygon fill="#1976d2" stroke="#1976d2" points="177.03,-429.28 171.62,-420.16 170.17,-430.66 177.03,-429.28"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="224.38" y="-475.53" font-family="Helvetica,sans-Serif" font-size="10.00">WebSocket</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- gateway -->
|
<!-- gateway -->
|
||||||
<g id="node3" class="node">
|
<g id="node4" class="node">
|
||||||
<title>gateway</title>
|
<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"/>
|
<path fill="#c8e6c9" stroke="black" d="M287.38,-284.62C287.38,-284.62 246.62,-284.62 246.62,-284.62 240.62,-284.62 234.62,-278.62 234.62,-272.62 234.62,-272.62 234.62,-260.62 234.62,-260.62 234.62,-254.62 240.62,-248.62 246.62,-248.62 246.62,-248.62 287.38,-248.62 287.38,-248.62 293.38,-248.62 299.38,-254.62 299.38,-260.62 299.38,-260.62 299.38,-272.62 299.38,-272.62 299.38,-278.62 293.38,-284.62 287.38,-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="267" 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>
|
<text xml:space="preserve" text-anchor="middle" x="267" y="-256.18" font-family="Helvetica,sans-Serif" font-size="11.00">(FastAPI)</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- browser->gateway -->
|
<!-- browser->gateway -->
|
||||||
<g id="edge1" class="edge">
|
<g id="edge3" class="edge">
|
||||||
<title>browser->gateway</title>
|
<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"/>
|
<path fill="none" stroke="#1976d2" stroke-dasharray="5,2" d="M258.62,-502.68C260.14,-459.9 264.1,-349.03 265.98,-296.3"/>
|
||||||
<polygon fill="#1976d2" stroke="#1976d2" points="119.02,-295.94 122.86,-286.06 113.7,-291.39 119.02,-295.94"/>
|
<polygon fill="#1976d2" stroke="#1976d2" points="269.46,-296.73 266.32,-286.61 262.47,-296.48 269.46,-296.73"/>
|
||||||
<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="290.17" y="-403.45" 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>
|
<text xml:space="preserve" text-anchor="middle" x="290.17" y="-390.7" font-family="Helvetica,sans-Serif" font-size="10.00">(local dev)</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- machines -->
|
<!-- machines -->
|
||||||
<g id="node2" class="node">
|
<g id="node2" class="node">
|
||||||
<title>machines</title>
|
<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"/>
|
<polygon fill="#fff3e0" stroke="black" points="389.75,-539.03 322.25,-539.03 318.25,-535.03 318.25,-503.03 385.75,-503.03 389.75,-507.03 389.75,-539.03"/>
|
||||||
<polyline fill="none" stroke="black" points="223.25,-414.2 155.75,-414.2"/>
|
<polyline fill="none" stroke="black" points="385.75,-535.03 318.25,-535.03"/>
|
||||||
<polyline fill="none" stroke="black" points="223.25,-414.2 223.25,-382.2"/>
|
<polyline fill="none" stroke="black" points="385.75,-535.03 385.75,-503.03"/>
|
||||||
<polyline fill="none" stroke="black" points="223.25,-414.2 227.25,-418.2"/>
|
<polyline fill="none" stroke="black" points="385.75,-535.03 389.75,-539.03"/>
|
||||||
<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="354" y="-524.08" 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>
|
<text xml:space="preserve" text-anchor="middle" x="354" y="-510.58" font-family="Helvetica,sans-Serif" font-size="11.00">Machines</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- collector -->
|
<!-- collector -->
|
||||||
<g id="node6" class="node">
|
<g id="node7" class="node">
|
||||||
<title>collector</title>
|
<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"/>
|
<path fill="#dcedc8" stroke="black" d="M394.38,-284.62C394.38,-284.62 329.62,-284.62 329.62,-284.62 323.62,-284.62 317.62,-278.62 317.62,-272.62 317.62,-272.62 317.62,-260.62 317.62,-260.62 317.62,-254.62 323.62,-248.62 329.62,-248.62 329.62,-248.62 394.38,-248.62 394.38,-248.62 400.38,-248.62 406.38,-254.62 406.38,-260.62 406.38,-260.62 406.38,-272.62 406.38,-272.62 406.38,-278.62 400.38,-284.62 394.38,-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="362" 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>
|
<text xml:space="preserve" text-anchor="middle" x="362" y="-256.18" font-family="Helvetica,sans-Serif" font-size="11.00">(gRPC Client)</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- machines->collector -->
|
<!-- machines->collector -->
|
||||||
<g id="edge5" class="edge">
|
<g id="edge7" class="edge">
|
||||||
<title>machines->collector</title>
|
<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"/>
|
<path fill="none" stroke="#f57c00" stroke-dasharray="1,5" d="M354.55,-502.68C355.91,-459.9 359.42,-349.03 361.09,-296.3"/>
|
||||||
<polygon fill="#f57c00" stroke="#f57c00" points="315.12,-296.47 312.08,-286.32 308.13,-296.15 315.12,-296.47"/>
|
<polygon fill="#f57c00" stroke="#f57c00" points="364.58,-296.71 361.4,-286.61 357.58,-296.49 364.58,-296.71"/>
|
||||||
<text xml:space="preserve" text-anchor="middle" x="318.1" y="-337.75" font-family="Helvetica,sans-Serif" font-size="10.00">psutil</text>
|
<text xml:space="preserve" text-anchor="middle" x="372.43" y="-397.08" font-family="Helvetica,sans-Serif" font-size="10.00">psutil</text>
|
||||||
|
</g>
|
||||||
|
<!-- edge_relay->gateway -->
|
||||||
|
<g id="edge2" class="edge">
|
||||||
|
<title>edge_relay->gateway</title>
|
||||||
|
<path fill="none" stroke="#1976d2" d="M177.03,-370.92C181.74,-357.23 188.58,-341 197.75,-328.12 209.78,-311.22 227.45,-295.88 241.93,-284.88"/>
|
||||||
|
<polygon fill="#1976d2" stroke="#1976d2" points="173.75,-369.67 174.03,-380.26 180.42,-371.81 173.75,-369.67"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="224.38" y="-344.12" font-family="Helvetica,sans-Serif" font-size="10.00">WebSocket</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="224.38" y="-331.38" font-family="Helvetica,sans-Serif" font-size="10.00">Forward</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- aggregator -->
|
<!-- aggregator -->
|
||||||
<g id="node4" class="node">
|
<g id="node5" class="node">
|
||||||
<title>aggregator</title>
|
<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"/>
|
<path fill="#c8e6c9" stroke="black" d="M412.62,-187.12C412.62,-187.12 343.38,-187.12 343.38,-187.12 337.38,-187.12 331.38,-181.12 331.38,-175.12 331.38,-175.12 331.38,-163.12 331.38,-163.12 331.38,-157.12 337.38,-151.12 343.38,-151.12 343.38,-151.12 412.62,-151.12 412.62,-151.12 418.62,-151.12 424.62,-157.12 424.62,-163.12 424.62,-163.12 424.62,-175.12 424.62,-175.12 424.62,-181.12 418.62,-187.12 412.62,-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="378" 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>
|
<text xml:space="preserve" text-anchor="middle" x="378" y="-158.68" font-family="Helvetica,sans-Serif" font-size="11.00">(gRPC Server)</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- gateway->aggregator -->
|
<!-- gateway->aggregator -->
|
||||||
<g id="edge2" class="edge">
|
<g id="edge4" class="edge">
|
||||||
<title>gateway->aggregator</title>
|
<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"/>
|
<path fill="none" stroke="#388e3c" d="M287.1,-248.33C304.44,-233.41 329.68,-211.69 349.17,-194.93"/>
|
||||||
<polygon fill="#388e3c" stroke="#388e3c" points="269.66,-196.37 276.6,-188.36 266.19,-190.29 269.66,-196.37"/>
|
<polygon fill="#388e3c" stroke="#388e3c" points="351.23,-197.78 356.53,-188.6 346.66,-192.47 351.23,-197.78"/>
|
||||||
<text xml:space="preserve" text-anchor="middle" x="257.62" y="-214.75" font-family="Helvetica,sans-Serif" font-size="10.00">gRPC</text>
|
<text xml:space="preserve" text-anchor="middle" x="348.46" y="-214.75" font-family="Helvetica,sans-Serif" font-size="10.00">gRPC</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- redis -->
|
<!-- redis -->
|
||||||
<g id="node7" class="node">
|
<g id="node8" class="node">
|
||||||
<title>redis</title>
|
<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="#ffecb3" stroke="black" d="M285.5,-59.75C285.5,-62.16 259.73,-64.12 228,-64.12 196.27,-64.12 170.5,-62.16 170.5,-59.75 170.5,-59.75 170.5,-20.38 170.5,-20.38 170.5,-17.96 196.27,-16 228,-16 259.73,-16 285.5,-17.96 285.5,-20.38 285.5,-20.38 285.5,-59.75 285.5,-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"/>
|
<path fill="none" stroke="black" d="M285.5,-59.75C285.5,-57.34 259.73,-55.38 228,-55.38 196.27,-55.38 170.5,-57.34 170.5,-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="228" 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>
|
<text xml:space="preserve" text-anchor="middle" x="228" y="-29.61" font-family="Helvetica,sans-Serif" font-size="11.00">(Pub/Sub + State)</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- gateway->redis -->
|
<!-- gateway->redis -->
|
||||||
<g id="edge3" class="edge">
|
<g id="edge5" class="edge">
|
||||||
<title>gateway->redis</title>
|
<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"/>
|
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M251.25,-248.13C238.89,-233.15 222.67,-210.37 215.75,-187.12 204.73,-150.09 211.09,-105.6 218.09,-75.53"/>
|
||||||
<polygon fill="black" stroke="black" points="83.14,-76.56 81.82,-66.04 76.29,-75.08 83.14,-76.56"/>
|
<polygon fill="black" stroke="black" points="221.49,-76.39 220.51,-65.84 214.7,-74.69 221.49,-76.39"/>
|
||||||
<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="230.38" 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>
|
<text xml:space="preserve" text-anchor="middle" x="230.38" y="-159.62" font-family="Helvetica,sans-Serif" font-size="10.00">Query</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- timescale -->
|
<!-- timescale -->
|
||||||
<g id="node8" class="node">
|
<g id="node9" class="node">
|
||||||
<title>timescale</title>
|
<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="#ffecb3" stroke="black" d="M392.38,-59.75C392.38,-62.16 372.49,-64.12 348,-64.12 323.51,-64.12 303.62,-62.16 303.62,-59.75 303.62,-59.75 303.62,-20.38 303.62,-20.38 303.62,-17.96 323.51,-16 348,-16 372.49,-16 392.38,-17.96 392.38,-20.38 392.38,-20.38 392.38,-59.75 392.38,-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"/>
|
<path fill="none" stroke="black" d="M392.38,-59.75C392.38,-57.34 372.49,-55.38 348,-55.38 323.51,-55.38 303.62,-57.34 303.62,-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="348" 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>
|
<text xml:space="preserve" text-anchor="middle" x="348" y="-29.61" font-family="Helvetica,sans-Serif" font-size="11.00">(Time-series)</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- gateway->timescale -->
|
<!-- gateway->timescale -->
|
||||||
<g id="edge4" class="edge">
|
<g id="edge6" class="edge">
|
||||||
<title>gateway->timescale</title>
|
<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"/>
|
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M266.24,-248.27C265.63,-224.24 266.08,-179.67 275.5,-143.12 282.34,-116.6 300.01,-91.45 316.2,-72.76"/>
|
||||||
<polygon fill="black" stroke="black" points="194.62,-76.29 195.83,-65.76 188.32,-73.24 194.62,-76.29"/>
|
<polygon fill="black" stroke="black" points="318.64,-75.28 322.72,-65.51 313.43,-70.6 318.64,-75.28"/>
|
||||||
<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="298.75" 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>
|
<text xml:space="preserve" text-anchor="middle" x="298.75" y="-159.62" font-family="Helvetica,sans-Serif" font-size="10.00">Query</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- aggregator->redis -->
|
<!-- aggregator->redis -->
|
||||||
<g id="edge7" class="edge">
|
<g id="edge9" class="edge">
|
||||||
<title>aggregator->redis</title>
|
<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"/>
|
<path fill="none" stroke="#ffa000" d="M348.99,-150.75C340.7,-145.41 331.8,-139.3 324,-133.12 300.45,-114.48 276.05,-91.01 257.74,-72.44"/>
|
||||||
<polygon fill="#ffa000" stroke="#ffa000" points="123.32,-69.73 113.56,-65.6 118.62,-74.91 123.32,-69.73"/>
|
<polygon fill="#ffa000" stroke="#ffa000" points="260.33,-70.08 250.84,-65.37 255.32,-74.97 260.33,-70.08"/>
|
||||||
<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="342" 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>
|
<text xml:space="preserve" text-anchor="middle" x="342" y="-110.88" font-family="Helvetica,sans-Serif" font-size="10.00">State</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- aggregator->timescale -->
|
<!-- aggregator->timescale -->
|
||||||
<g id="edge8" class="edge">
|
<g id="edge10" class="edge">
|
||||||
<title>aggregator->timescale</title>
|
<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"/>
|
<path fill="none" stroke="#ffa000" d="M373.89,-150.72C369.32,-131.36 361.82,-99.6 356.07,-75.22"/>
|
||||||
<polygon fill="#ffa000" stroke="#ffa000" points="236.64,-71.16 227.71,-65.47 231.14,-75.49 236.64,-71.16"/>
|
<polygon fill="#ffa000" stroke="#ffa000" points="359.53,-74.68 353.83,-65.75 352.72,-76.29 359.53,-74.68"/>
|
||||||
<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="387.14" 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>
|
<text xml:space="preserve" text-anchor="middle" x="387.14" y="-110.88" font-family="Helvetica,sans-Serif" font-size="10.00">Metrics</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- events -->
|
<!-- events -->
|
||||||
<g id="node9" class="node">
|
<g id="node10" class="node">
|
||||||
<title>events</title>
|
<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"/>
|
<path fill="#e1bee7" stroke="black" d="M543.13,-407.37C543.13,-407.37 524,-421.61 524,-421.61 519.19,-425.2 508.38,-428.78 502.38,-428.78 502.38,-428.78 449.62,-428.78 449.62,-428.78 443.62,-428.78 432.81,-425.2 428,-421.61 428,-421.61 408.87,-407.37 408.87,-407.37 404.06,-403.79 404.06,-396.62 408.87,-393.04 408.87,-393.04 428,-378.79 428,-378.79 432.81,-375.21 443.62,-371.62 449.62,-371.62 449.62,-371.62 502.38,-371.62 502.38,-371.62 508.38,-371.62 519.19,-375.21 524,-378.79 524,-378.79 543.13,-393.04 543.13,-393.04 547.94,-396.62 547.94,-403.79 543.13,-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="476" 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>
|
<text xml:space="preserve" text-anchor="middle" x="476" y="-389.75" font-family="Helvetica,sans-Serif" font-size="11.00">(Events)</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- aggregator->events -->
|
<!-- aggregator->events -->
|
||||||
<g id="edge9" class="edge">
|
<g id="edge11" class="edge">
|
||||||
<title>aggregator->events</title>
|
<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"/>
|
<path fill="none" stroke="#7b1fa2" d="M416.67,-187.52C441.53,-200.73 472.36,-221.29 490,-248.62 495.8,-257.61 513.79,-323.42 505,-353.62 504.25,-356.21 503.3,-358.78 502.21,-361.32"/>
|
||||||
<polygon fill="#7b1fa2" stroke="#7b1fa2" points="346.73,-359.44 345.42,-369.95 353,-362.55 346.73,-359.44"/>
|
<polygon fill="#7b1fa2" stroke="#7b1fa2" points="499.15,-359.62 497.75,-370.12 505.39,-362.79 499.15,-359.62"/>
|
||||||
<text xml:space="preserve" text-anchor="middle" x="386.64" y="-263.5" font-family="Helvetica,sans-Serif" font-size="10.00">Publish</text>
|
<text xml:space="preserve" text-anchor="middle" x="524.36" y="-263.5" font-family="Helvetica,sans-Serif" font-size="10.00">Publish</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- alerts -->
|
<!-- alerts -->
|
||||||
<g id="node5" class="node">
|
<g id="node6" class="node">
|
||||||
<title>alerts</title>
|
<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"/>
|
<path fill="#c8e6c9" stroke="black" d="M469.25,-284.62C469.25,-284.62 436.75,-284.62 436.75,-284.62 430.75,-284.62 424.75,-278.62 424.75,-272.62 424.75,-272.62 424.75,-260.62 424.75,-260.62 424.75,-254.62 430.75,-248.62 436.75,-248.62 436.75,-248.62 469.25,-248.62 469.25,-248.62 475.25,-248.62 481.25,-254.62 481.25,-260.62 481.25,-260.62 481.25,-272.62 481.25,-272.62 481.25,-278.62 475.25,-284.62 469.25,-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="453" 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>
|
<text xml:space="preserve" text-anchor="middle" x="453" 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>
|
</g>
|
||||||
<!-- collector->aggregator -->
|
<!-- collector->aggregator -->
|
||||||
<g id="edge6" class="edge">
|
<g id="edge8" class="edge">
|
||||||
<title>collector->aggregator</title>
|
<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"/>
|
<path fill="none" stroke="#388e3c" d="M364.86,-248.55C367.19,-234.65 370.53,-214.73 373.25,-198.45"/>
|
||||||
<polygon fill="#388e3c" stroke="#388e3c" points="312.9,-198.77 309.09,-188.89 305.91,-198.99 312.9,-198.77"/>
|
<polygon fill="#388e3c" stroke="#388e3c" points="376.66,-199.31 374.86,-188.87 369.76,-198.15 376.66,-199.31"/>
|
||||||
<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="389.53" 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>
|
<text xml:space="preserve" text-anchor="middle" x="389.53" y="-208.38" font-family="Helvetica,sans-Serif" font-size="10.00">Stream</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- events->gateway -->
|
<!-- events->gateway -->
|
||||||
<g id="edge11" class="edge">
|
<g id="edge13" class="edge">
|
||||||
<title>events->gateway</title>
|
<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"/>
|
<path fill="none" stroke="#7b1fa2" d="M427.23,-379.02C385.24,-361.12 328.44,-335.5 309,-320.12 299.76,-312.82 291.28,-303.11 284.39,-294.03"/>
|
||||||
<polygon fill="#7b1fa2" stroke="#7b1fa2" points="149.38,-295.39 143.95,-286.29 142.52,-296.79 149.38,-295.39"/>
|
<polygon fill="#7b1fa2" stroke="#7b1fa2" points="287.4,-292.22 278.71,-286.15 281.72,-296.31 287.4,-292.22"/>
|
||||||
<text xml:space="preserve" text-anchor="middle" x="200.88" y="-337.75" font-family="Helvetica,sans-Serif" font-size="10.00">Subscribe</text>
|
<text xml:space="preserve" text-anchor="middle" x="391.72" y="-337.75" font-family="Helvetica,sans-Serif" font-size="10.00">Subscribe</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- events->alerts -->
|
<!-- events->alerts -->
|
||||||
<g id="edge10" class="edge">
|
<g id="edge12" class="edge">
|
||||||
<title>events->alerts</title>
|
<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"/>
|
<path fill="none" stroke="#7b1fa2" d="M463.14,-371.21C460.99,-365.5 459.04,-359.45 457.75,-353.62 453.59,-334.83 452.41,-313.19 452.27,-296.31"/>
|
||||||
<polygon fill="#7b1fa2" stroke="#7b1fa2" points="225.38,-296.07 220.98,-286.43 218.41,-296.71 225.38,-296.07"/>
|
<polygon fill="#7b1fa2" stroke="#7b1fa2" points="455.77,-296.51 452.31,-286.49 448.77,-296.48 455.77,-296.51"/>
|
||||||
<text xml:space="preserve" text-anchor="middle" x="265.88" y="-337.75" font-family="Helvetica,sans-Serif" font-size="10.00">Subscribe</text>
|
<text xml:space="preserve" text-anchor="middle" x="481.38" y="-337.75" font-family="Helvetica,sans-Serif" font-size="10.00">Subscribe</text>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 17 KiB |
@@ -60,7 +60,16 @@ digraph DataFlow {
|
|||||||
|
|
||||||
alerts [label="Alert\nService", fillcolor="#C5CAE9"];
|
alerts [label="Alert\nService", fillcolor="#C5CAE9"];
|
||||||
gateway [label="Gateway\n(WebSocket)", fillcolor="#9FA8DA"];
|
gateway [label="Gateway\n(WebSocket)", fillcolor="#9FA8DA"];
|
||||||
lambda [label="Lambda\nAggregator", fillcolor="#7986CB", style="rounded,filled,dashed"];
|
}
|
||||||
|
|
||||||
|
// Edge + Browser
|
||||||
|
subgraph cluster_delivery {
|
||||||
|
label="Delivery (AWS)";
|
||||||
|
style=filled;
|
||||||
|
fillcolor="#F3E5F5";
|
||||||
|
|
||||||
|
edge_relay [label="Edge\n(WS Relay)", fillcolor="#E1BEE7"];
|
||||||
|
browser [label="Browser\n(Dashboard)", fillcolor="#CE93D8"];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flow
|
// Flow
|
||||||
@@ -75,9 +84,9 @@ digraph DataFlow {
|
|||||||
redis_pubsub -> alerts [label="metrics.*"];
|
redis_pubsub -> alerts [label="metrics.*"];
|
||||||
redis_pubsub -> gateway [label="metrics.*"];
|
redis_pubsub -> gateway [label="metrics.*"];
|
||||||
|
|
||||||
|
gateway -> edge_relay [label="WebSocket\nForward"];
|
||||||
|
edge_relay -> browser [label="WebSocket"];
|
||||||
|
|
||||||
raw -> agg_1m [label="Continuous\nAggregate", style=dashed];
|
raw -> agg_1m [label="Continuous\nAggregate", style=dashed];
|
||||||
agg_1m -> agg_1h [label="Hourly\nJob", 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];
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,134 +1,139 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
<!-- Generated by graphviz version 14.1.1 (0)
|
<!-- Generated by graphviz version 14.1.2 (0)
|
||||||
-->
|
-->
|
||||||
<!-- Title: DataFlow Pages: 1 -->
|
<!-- Title: DataFlow Pages: 1 -->
|
||||||
<svg width="1087pt" height="329pt"
|
<svg width="1270pt" height="305pt"
|
||||||
viewBox="0.00 0.00 1087.00 329.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
viewBox="0.00 0.00 1270.00 305.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)">
|
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 301.25)">
|
||||||
<title>DataFlow</title>
|
<title>DataFlow</title>
|
||||||
<polygon fill="white" stroke="none" points="-4,4 -4,-325.25 1082.5,-325.25 1082.5,4 -4,4"/>
|
<polygon fill="white" stroke="none" points="-4,4 -4,-301.25 1265.75,-301.25 1265.75,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>
|
<text xml:space="preserve" text-anchor="middle" x="630.88" y="-279.95" font-family="Helvetica,sans-Serif" font-size="14.00">Metrics Data Flow Pipeline</text>
|
||||||
<g id="clust1" class="cluster">
|
<g id="clust1" class="cluster">
|
||||||
<title>cluster_collect</title>
|
<title>cluster_collect</title>
|
||||||
<polygon fill="#e3f2fd" stroke="black" points="8,-111 8,-188 254,-188 254,-111 8,-111"/>
|
<polygon fill="#e3f2fd" stroke="black" points="8,-87 8,-164 254,-164 254,-87 8,-87"/>
|
||||||
<text xml:space="preserve" text-anchor="middle" x="131" y="-170.7" font-family="Helvetica,sans-Serif" font-size="14.00">Collection (5s)</text>
|
<text xml:space="preserve" text-anchor="middle" x="131" y="-146.7" font-family="Helvetica,sans-Serif" font-size="14.00">Collection (5s)</text>
|
||||||
</g>
|
</g>
|
||||||
<g id="clust2" class="cluster">
|
<g id="clust2" class="cluster">
|
||||||
<title>cluster_ingest</title>
|
<title>cluster_ingest</title>
|
||||||
<polygon fill="#e8f5e9" stroke="black" points="307,-95 307,-204 562.5,-204 562.5,-95 307,-95"/>
|
<polygon fill="#e8f5e9" stroke="black" points="307,-71 307,-180 562.5,-180 562.5,-71 307,-71"/>
|
||||||
<text xml:space="preserve" text-anchor="middle" x="434.75" y="-186.7" font-family="Helvetica,sans-Serif" font-size="14.00">Ingestion</text>
|
<text xml:space="preserve" text-anchor="middle" x="434.75" y="-162.7" font-family="Helvetica,sans-Serif" font-size="14.00">Ingestion</text>
|
||||||
</g>
|
</g>
|
||||||
<g id="clust3" class="cluster">
|
<g id="clust3" class="cluster">
|
||||||
<title>cluster_hot</title>
|
<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"/>
|
<polygon fill="#fff3e0" stroke="black" points="614.75,-10 614.75,-169 769.5,-169 769.5,-10 614.75,-10"/>
|
||||||
<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>
|
<text xml:space="preserve" text-anchor="middle" x="692.12" y="-151.7" font-family="Helvetica,sans-Serif" font-size="14.00">Hot Path (Real-time)</text>
|
||||||
</g>
|
</g>
|
||||||
<g id="clust4" class="cluster">
|
<g id="clust4" class="cluster">
|
||||||
<title>cluster_warm</title>
|
<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"/>
|
<polygon fill="#fce4ec" stroke="black" points="645.62,-177 645.62,-264 1091.5,-264 1091.5,-177 645.62,-177"/>
|
||||||
<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>
|
<text xml:space="preserve" text-anchor="middle" x="868.56" y="-246.7" font-family="Helvetica,sans-Serif" font-size="14.00">Warm Path (Historical)</text>
|
||||||
</g>
|
</g>
|
||||||
<g id="clust5" class="cluster">
|
<g id="clust5" class="cluster">
|
||||||
<title>cluster_consume</title>
|
<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"/>
|
<polygon fill="#e8eaf6" stroke="black" points="840.5,-8 840.5,-139 935.25,-139 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>
|
<text xml:space="preserve" text-anchor="middle" x="887.88" y="-121.7" font-family="Helvetica,sans-Serif" font-size="14.00">Consumers</text>
|
||||||
|
</g>
|
||||||
|
<g id="clust6" class="cluster">
|
||||||
|
<title>cluster_delivery</title>
|
||||||
|
<polygon fill="#f3e5f5" stroke="black" points="1005.5,-8 1005.5,-85 1253.75,-85 1253.75,-8 1005.5,-8"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1129.62" y="-67.7" font-family="Helvetica,sans-Serif" font-size="14.00">Delivery (AWS)</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- psutil -->
|
<!-- psutil -->
|
||||||
<g id="node1" class="node">
|
<g id="node1" class="node">
|
||||||
<title>psutil</title>
|
<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"/>
|
<polygon fill="#bbdefb" stroke="black" points="118.25,-131 16,-131 16,-127 12,-127 12,-123 16,-123 16,-103 12,-103 12,-99 16,-99 16,-95 118.25,-95 118.25,-131"/>
|
||||||
<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"/>
|
<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>
|
<polyline fill="none" stroke="black" points="16,-103 20,-103 20,-99 16,-99"/>
|
||||||
<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>
|
<text xml:space="preserve" text-anchor="middle" x="67.12" y="-116.25" font-family="Helvetica,sans-Serif" font-size="10.00">psutil</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="67.12" y="-103.5" font-family="Helvetica,sans-Serif" font-size="10.00">(CPU, Mem, Disk)</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- collector -->
|
<!-- collector -->
|
||||||
<g id="node2" class="node">
|
<g id="node2" class="node">
|
||||||
<title>collector</title>
|
<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"/>
|
<path fill="#90caf9" stroke="black" d="M234,-131C234,-131 198.5,-131 198.5,-131 192.5,-131 186.5,-125 186.5,-119 186.5,-119 186.5,-107 186.5,-107 186.5,-101 192.5,-95 198.5,-95 198.5,-95 234,-95 234,-95 240,-95 246,-101 246,-107 246,-107 246,-119 246,-119 246,-125 240,-131 234,-131"/>
|
||||||
<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="-116.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>
|
<text xml:space="preserve" text-anchor="middle" x="216.25" y="-103.5" font-family="Helvetica,sans-Serif" font-size="10.00">Service</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- psutil->collector -->
|
<!-- psutil->collector -->
|
||||||
<g id="edge1" class="edge">
|
<g id="edge1" class="edge">
|
||||||
<title>psutil->collector</title>
|
<title>psutil->collector</title>
|
||||||
<path fill="none" stroke="black" d="M118.35,-137C136.74,-137 157.31,-137 174.75,-137"/>
|
<path fill="none" stroke="black" d="M118.35,-113C136.74,-113 157.31,-113 174.75,-113"/>
|
||||||
<polygon fill="black" stroke="black" points="174.75,-140.5 184.75,-137 174.75,-133.5 174.75,-140.5"/>
|
<polygon fill="black" stroke="black" points="174.75,-116.5 184.75,-113 174.75,-109.5 174.75,-116.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>
|
<text xml:space="preserve" text-anchor="middle" x="152.38" y="-115.7" font-family="Helvetica,sans-Serif" font-size="9.00">Metrics</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- aggregator -->
|
<!-- aggregator -->
|
||||||
<g id="node3" class="node">
|
<g id="node3" class="node">
|
||||||
<title>aggregator</title>
|
<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"/>
|
<path fill="#a5d6a7" stroke="black" d="M373,-131C373,-131 327,-131 327,-131 321,-131 315,-125 315,-119 315,-119 315,-107 315,-107 315,-101 321,-95 327,-95 327,-95 373,-95 373,-95 379,-95 385,-101 385,-107 385,-107 385,-119 385,-119 385,-125 379,-131 373,-131"/>
|
||||||
<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="-116.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>
|
<text xml:space="preserve" text-anchor="middle" x="350" y="-103.5" font-family="Helvetica,sans-Serif" font-size="10.00">(gRPC)</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- collector->aggregator -->
|
<!-- collector->aggregator -->
|
||||||
<g id="edge2" class="edge">
|
<g id="edge2" class="edge">
|
||||||
<title>collector->aggregator</title>
|
<title>collector->aggregator</title>
|
||||||
<path fill="none" stroke="black" d="M246.49,-137C263.19,-137 284.49,-137 303.35,-137"/>
|
<path fill="none" stroke="black" d="M246.49,-113C263.19,-113 284.49,-113 303.35,-113"/>
|
||||||
<polygon fill="black" stroke="black" points="303.2,-140.5 313.2,-137 303.2,-133.5 303.2,-140.5"/>
|
<polygon fill="black" stroke="black" points="303.2,-116.5 313.2,-113 303.2,-109.5 303.2,-116.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="-126.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>
|
<text xml:space="preserve" text-anchor="middle" x="280.5" y="-115.7" font-family="Helvetica,sans-Serif" font-size="9.00">Stream</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- validate -->
|
<!-- validate -->
|
||||||
<g id="node4" class="node">
|
<g id="node4" class="node">
|
||||||
<title>validate</title>
|
<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"/>
|
<path fill="#c8e6c9" stroke="black" d="M477.54,-141.08C477.54,-141.08 432.71,-118.42 432.71,-118.42 427.35,-115.71 427.35,-110.29 432.71,-107.58 432.71,-107.58 477.54,-84.92 477.54,-84.92 482.9,-82.21 493.6,-82.21 498.96,-84.92 498.96,-84.92 543.79,-107.58 543.79,-107.58 549.15,-110.29 549.15,-115.71 543.79,-118.42 543.79,-118.42 498.96,-141.08 498.96,-141.08 493.6,-143.79 482.9,-143.79 477.54,-141.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="-116.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>
|
<text xml:space="preserve" text-anchor="middle" x="488.25" y="-103.5" font-family="Helvetica,sans-Serif" font-size="10.00">Normalize</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- aggregator->validate -->
|
<!-- aggregator->validate -->
|
||||||
<g id="edge3" class="edge">
|
<g id="edge3" class="edge">
|
||||||
<title>aggregator->validate</title>
|
<title>aggregator->validate</title>
|
||||||
<path fill="none" stroke="black" d="M385.38,-137C392.95,-137 401.25,-137 409.76,-137"/>
|
<path fill="none" stroke="black" d="M385.38,-113C392.95,-113 401.25,-113 409.76,-113"/>
|
||||||
<polygon fill="black" stroke="black" points="409.49,-140.5 419.49,-137 409.49,-133.5 409.49,-140.5"/>
|
<polygon fill="black" stroke="black" points="409.49,-116.5 419.49,-113 409.49,-109.5 409.49,-116.5"/>
|
||||||
</g>
|
</g>
|
||||||
<!-- redis_state -->
|
<!-- redis_state -->
|
||||||
<g id="node5" class="node">
|
<g id="node5" class="node">
|
||||||
<title>redis_state</title>
|
<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="#ffcc80" stroke="black" d="M731.88,-131.84C731.88,-134.15 713.83,-136.03 691.62,-136.03 669.42,-136.03 651.38,-134.15 651.38,-131.84 651.38,-131.84 651.38,-94.16 651.38,-94.16 651.38,-91.85 669.42,-89.97 691.62,-89.97 713.83,-89.97 731.88,-91.85 731.88,-94.16 731.88,-94.16 731.88,-131.84 731.88,-131.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"/>
|
<path fill="none" stroke="black" d="M731.88,-131.84C731.88,-129.53 713.83,-127.66 691.62,-127.66 669.42,-127.66 651.38,-129.53 651.38,-131.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="-116.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>
|
<text xml:space="preserve" text-anchor="middle" x="691.62" y="-103.5" font-family="Helvetica,sans-Serif" font-size="10.00">Current State</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- validate->redis_state -->
|
<!-- validate->redis_state -->
|
||||||
<g id="edge4" class="edge">
|
<g id="edge4" class="edge">
|
||||||
<title>validate->redis_state</title>
|
<title>validate->redis_state</title>
|
||||||
<path fill="none" stroke="black" d="M555.47,-137C582.9,-137 614.22,-137 639.8,-137"/>
|
<path fill="none" stroke="black" d="M555.47,-113C582.9,-113 614.22,-113 639.8,-113"/>
|
||||||
<polygon fill="black" stroke="black" points="639.6,-140.5 649.6,-137 639.6,-133.5 639.6,-140.5"/>
|
<polygon fill="black" stroke="black" points="639.6,-116.5 649.6,-113 639.6,-109.5 639.6,-116.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>
|
<text xml:space="preserve" text-anchor="middle" x="588.63" y="-115.7" font-family="Helvetica,sans-Serif" font-size="9.00">Upsert</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- redis_pubsub -->
|
<!-- redis_pubsub -->
|
||||||
<g id="node6" class="node">
|
<g id="node6" class="node">
|
||||||
<title>redis_pubsub</title>
|
<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"/>
|
<path fill="#ffb74d" stroke="black" d="M729.05,-54.12C729.05,-54.12 721.56,-63.24 721.56,-63.24 717.82,-67.79 708.18,-72.35 702.28,-72.35 702.28,-72.35 680.97,-72.35 680.97,-72.35 675.07,-72.35 665.43,-67.79 661.69,-63.24 661.69,-63.24 654.2,-54.12 654.2,-54.12 650.46,-49.56 650.46,-40.44 654.2,-35.88 654.2,-35.88 661.69,-26.76 661.69,-26.76 665.43,-22.21 675.07,-17.65 680.97,-17.65 680.97,-17.65 702.28,-17.65 702.28,-17.65 708.18,-17.65 717.82,-22.21 721.56,-26.76 721.56,-26.76 729.05,-35.88 729.05,-35.88 732.79,-40.44 732.79,-49.56 729.05,-54.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="-48.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>
|
<text xml:space="preserve" text-anchor="middle" x="691.62" y="-35.5" font-family="Helvetica,sans-Serif" font-size="10.00">Pub/Sub</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- validate->redis_pubsub -->
|
<!-- validate->redis_pubsub -->
|
||||||
<g id="edge5" class="edge">
|
<g id="edge5" class="edge">
|
||||||
<title>validate->redis_pubsub</title>
|
<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"/>
|
<path fill="none" stroke="black" d="M529.04,-99.57C562.44,-88.28 610.18,-72.17 645.1,-60.37"/>
|
||||||
<polygon fill="black" stroke="black" points="646.17,-87.71 654.53,-81.19 643.93,-81.07 646.17,-87.71"/>
|
<polygon fill="black" stroke="black" points="646.17,-63.71 654.53,-57.19 643.93,-57.07 646.17,-63.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>
|
<text xml:space="preserve" text-anchor="middle" x="588.63" y="-85.77" font-family="Helvetica,sans-Serif" font-size="9.00">Publish</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- raw -->
|
<!-- raw -->
|
||||||
<g id="node7" class="node">
|
<g id="node7" class="node">
|
||||||
<title>raw</title>
|
<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="#f8bbd9" stroke="black" d="M729.62,-226.84C729.62,-229.15 712.59,-231.03 691.62,-231.03 670.66,-231.03 653.62,-229.15 653.62,-226.84 653.62,-226.84 653.62,-189.16 653.62,-189.16 653.62,-186.85 670.66,-184.97 691.62,-184.97 712.59,-184.97 729.62,-186.85 729.62,-189.16 729.62,-189.16 729.62,-226.84 729.62,-226.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"/>
|
<path fill="none" stroke="black" d="M729.62,-226.84C729.62,-224.53 712.59,-222.66 691.62,-222.66 670.66,-222.66 653.62,-224.53 653.62,-226.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="-211.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>
|
<text xml:space="preserve" text-anchor="middle" x="691.62" y="-198.5" font-family="Helvetica,sans-Serif" font-size="10.00">(5s, 24h)</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- validate->raw -->
|
<!-- validate->raw -->
|
||||||
<g id="edge6" class="edge">
|
<g id="edge6" class="edge">
|
||||||
<title>validate->raw</title>
|
<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"/>
|
<path fill="none" stroke="black" d="M523.01,-129.3C548.24,-141.44 583.6,-158.37 614.75,-173 623.81,-177.26 633.5,-181.76 642.83,-186.07"/>
|
||||||
<polygon fill="black" stroke="black" points="641.22,-213.19 651.77,-214.2 644.16,-206.83 641.22,-213.19"/>
|
<polygon fill="black" stroke="black" points="641.22,-189.19 651.77,-190.2 644.16,-182.83 641.22,-189.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>
|
<text xml:space="preserve" text-anchor="middle" x="588.63" y="-170.9" font-family="Helvetica,sans-Serif" font-size="9.00">Insert</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- alerts -->
|
<!-- alerts -->
|
||||||
<g id="node10" class="node">
|
<g id="node10" class="node">
|
||||||
@@ -140,9 +145,9 @@
|
|||||||
<!-- redis_pubsub->alerts -->
|
<!-- redis_pubsub->alerts -->
|
||||||
<g id="edge7" class="edge">
|
<g id="edge7" class="edge">
|
||||||
<title>redis_pubsub->alerts</title>
|
<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"/>
|
<path fill="none" stroke="black" d="M729.98,-53.29C764.26,-60.9 814.78,-72.11 849.05,-79.72"/>
|
||||||
<polygon fill="black" stroke="black" points="848.11,-87.76 858.4,-85.26 848.79,-80.8 848.11,-87.76"/>
|
<polygon fill="black" stroke="black" points="848.01,-83.07 858.53,-81.82 849.52,-76.24 848.01,-83.07"/>
|
||||||
<text xml:space="preserve" text-anchor="middle" x="805" y="-85.09" font-family="Helvetica,sans-Serif" font-size="9.00">metrics.*</text>
|
<text xml:space="preserve" text-anchor="middle" x="805" y="-77.99" font-family="Helvetica,sans-Serif" font-size="9.00">metrics.*</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- gateway -->
|
<!-- gateway -->
|
||||||
<g id="node11" class="node">
|
<g id="node11" class="node">
|
||||||
@@ -154,64 +159,70 @@
|
|||||||
<!-- redis_pubsub->gateway -->
|
<!-- redis_pubsub->gateway -->
|
||||||
<g id="edge8" class="edge">
|
<g id="edge8" class="edge">
|
||||||
<title>redis_pubsub->gateway</title>
|
<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"/>
|
<path fill="none" stroke="black" d="M735.14,-42.59C765.3,-40.87 805.86,-38.57 837.38,-36.78"/>
|
||||||
<polygon fill="black" stroke="black" points="837.98,-46.3 847.2,-41.08 836.74,-39.41 837.98,-46.3"/>
|
<polygon fill="black" stroke="black" points="837.25,-40.29 847.03,-36.23 836.85,-33.31 837.25,-40.29"/>
|
||||||
<text xml:space="preserve" text-anchor="middle" x="805" y="-55.25" font-family="Helvetica,sans-Serif" font-size="9.00">metrics.*</text>
|
<text xml:space="preserve" text-anchor="middle" x="805" y="-42.53" font-family="Helvetica,sans-Serif" font-size="9.00">metrics.*</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- agg_1m -->
|
<!-- agg_1m -->
|
||||||
<g id="node8" class="node">
|
<g id="node8" class="node">
|
||||||
<title>agg_1m</title>
|
<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="#f48fb1" stroke="black" d="M924.25,-226.84C924.25,-229.15 907.72,-231.03 887.38,-231.03 867.03,-231.03 850.5,-229.15 850.5,-226.84 850.5,-226.84 850.5,-189.16 850.5,-189.16 850.5,-186.85 867.03,-184.97 887.38,-184.97 907.72,-184.97 924.25,-186.85 924.25,-189.16 924.25,-189.16 924.25,-226.84 924.25,-226.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"/>
|
<path fill="none" stroke="black" d="M924.25,-226.84C924.25,-224.53 907.72,-222.66 887.38,-222.66 867.03,-222.66 850.5,-224.53 850.5,-226.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="-211.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>
|
<text xml:space="preserve" text-anchor="middle" x="887.38" y="-198.5" font-family="Helvetica,sans-Serif" font-size="10.00">(1m, 7d)</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- raw->agg_1m -->
|
<!-- 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">
|
<g id="edge11" class="edge">
|
||||||
<title>raw->lambda</title>
|
<title>raw->agg_1m</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"/>
|
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M729.98,-208C760.97,-208 805.22,-208 838.74,-208"/>
|
||||||
<polygon fill="black" stroke="black" points="843,-166.44 850.54,-159.01 840,-160.12 843,-166.44"/>
|
<polygon fill="black" stroke="black" points="838.6,-211.5 848.6,-208 838.6,-204.5 838.6,-211.5"/>
|
||||||
<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="-221.95" font-family="Helvetica,sans-Serif" font-size="9.00">Continuous</text>
|
||||||
<text xml:space="preserve" text-anchor="middle" x="805" y="-193.8" font-family="Helvetica,sans-Serif" font-size="9.00">Trigger</text>
|
<text xml:space="preserve" text-anchor="middle" x="805" y="-210.7" font-family="Helvetica,sans-Serif" font-size="9.00">Aggregate</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- agg_1h -->
|
<!-- agg_1h -->
|
||||||
<g id="node9" class="node">
|
<g id="node9" class="node">
|
||||||
<title>agg_1h</title>
|
<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="#ec407a" stroke="black" d="M1083.5,-226.84C1083.5,-229.15 1067.81,-231.03 1048.5,-231.03 1029.19,-231.03 1013.5,-229.15 1013.5,-226.84 1013.5,-226.84 1013.5,-189.16 1013.5,-189.16 1013.5,-186.85 1029.19,-184.97 1048.5,-184.97 1067.81,-184.97 1083.5,-186.85 1083.5,-189.16 1083.5,-189.16 1083.5,-226.84 1083.5,-226.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"/>
|
<path fill="none" stroke="black" d="M1083.5,-226.84C1083.5,-224.53 1067.81,-222.66 1048.5,-222.66 1029.19,-222.66 1013.5,-224.53 1013.5,-226.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="1048.5" y="-211.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>
|
<text xml:space="preserve" text-anchor="middle" x="1048.5" y="-198.5" font-family="Helvetica,sans-Serif" font-size="10.00">(1h, 90d)</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- agg_1m->agg_1h -->
|
<!-- 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">
|
<g id="edge12" class="edge">
|
||||||
<title>lambda->agg_1m</title>
|
<title>agg_1m->agg_1h</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"/>
|
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M924.48,-208C947.44,-208 977.36,-208 1001.94,-208"/>
|
||||||
<polygon fill="black" stroke="black" points="883.88,-197.16 887.38,-207.16 890.88,-197.16 883.88,-197.16"/>
|
<polygon fill="black" stroke="black" points="1001.66,-211.5 1011.66,-208 1001.66,-204.5 1001.66,-211.5"/>
|
||||||
<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="970.38" y="-221.95" font-family="Helvetica,sans-Serif" font-size="9.00">Hourly</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>
|
<text xml:space="preserve" text-anchor="middle" x="970.38" y="-210.7" font-family="Helvetica,sans-Serif" font-size="9.00">Job</text>
|
||||||
|
</g>
|
||||||
|
<!-- edge_relay -->
|
||||||
|
<g id="node12" class="node">
|
||||||
|
<title>edge_relay</title>
|
||||||
|
<path fill="#e1bee7" stroke="black" d="M1071.5,-52C1071.5,-52 1025.5,-52 1025.5,-52 1019.5,-52 1013.5,-46 1013.5,-40 1013.5,-40 1013.5,-28 1013.5,-28 1013.5,-22 1019.5,-16 1025.5,-16 1025.5,-16 1071.5,-16 1071.5,-16 1077.5,-16 1083.5,-22 1083.5,-28 1083.5,-28 1083.5,-40 1083.5,-40 1083.5,-46 1077.5,-52 1071.5,-52"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1048.5" y="-37.25" font-family="Helvetica,sans-Serif" font-size="10.00">Edge</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1048.5" y="-24.5" font-family="Helvetica,sans-Serif" font-size="10.00">(WS Relay)</text>
|
||||||
|
</g>
|
||||||
|
<!-- gateway->edge_relay -->
|
||||||
|
<g id="edge9" class="edge">
|
||||||
|
<title>gateway->edge_relay</title>
|
||||||
|
<path fill="none" stroke="black" d="M926.09,-34C948.75,-34 977.76,-34 1001.73,-34"/>
|
||||||
|
<polygon fill="black" stroke="black" points="1001.53,-37.5 1011.53,-34 1001.53,-30.5 1001.53,-37.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="970.38" y="-47.95" font-family="Helvetica,sans-Serif" font-size="9.00">WebSocket</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="970.38" y="-36.7" font-family="Helvetica,sans-Serif" font-size="9.00">Forward</text>
|
||||||
|
</g>
|
||||||
|
<!-- browser -->
|
||||||
|
<g id="node13" class="node">
|
||||||
|
<title>browser</title>
|
||||||
|
<path fill="#ce93d8" stroke="black" d="M1233.75,-52C1233.75,-52 1181.75,-52 1181.75,-52 1175.75,-52 1169.75,-46 1169.75,-40 1169.75,-40 1169.75,-28 1169.75,-28 1169.75,-22 1175.75,-16 1181.75,-16 1181.75,-16 1233.75,-16 1233.75,-16 1239.75,-16 1245.75,-22 1245.75,-28 1245.75,-28 1245.75,-40 1245.75,-40 1245.75,-46 1239.75,-52 1233.75,-52"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1207.75" y="-37.25" font-family="Helvetica,sans-Serif" font-size="10.00">Browser</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1207.75" y="-24.5" font-family="Helvetica,sans-Serif" font-size="10.00">(Dashboard)</text>
|
||||||
|
</g>
|
||||||
|
<!-- edge_relay->browser -->
|
||||||
|
<g id="edge10" class="edge">
|
||||||
|
<title>edge_relay->browser</title>
|
||||||
|
<path fill="none" stroke="black" d="M1083.62,-34C1105.36,-34 1133.86,-34 1157.96,-34"/>
|
||||||
|
<polygon fill="black" stroke="black" points="1157.88,-37.5 1167.88,-34 1157.88,-30.5 1157.88,-37.5"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="1126.62" y="-36.7" font-family="Helvetica,sans-Serif" font-size="9.00">WebSocket</text>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 18 KiB |
@@ -11,60 +11,36 @@ digraph Deployment {
|
|||||||
|
|
||||||
node [shape=box, style="rounded,filled"];
|
node [shape=box, style="rounded,filled"];
|
||||||
|
|
||||||
// Local Development
|
// Local Stack
|
||||||
subgraph cluster_local {
|
subgraph cluster_local {
|
||||||
label="Local Development";
|
label="Local Stack (Docker Compose)";
|
||||||
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;
|
style=filled;
|
||||||
fillcolor="#E8F5E9";
|
fillcolor="#E8F5E9";
|
||||||
|
|
||||||
subgraph cluster_ec2 {
|
aggregator [label="Aggregator\n(gRPC Server)", fillcolor="#A5D6A7"];
|
||||||
label="EC2 t2.small";
|
gateway [label="Gateway\n(FastAPI)", fillcolor="#A5D6A7"];
|
||||||
style=filled;
|
alerts [label="Alerts\nService", fillcolor="#A5D6A7"];
|
||||||
fillcolor="#C8E6C9";
|
redis [label="Redis", shape=cylinder, fillcolor="#C8E6C9"];
|
||||||
|
timescaledb [label="TimescaleDB", shape=cylinder, fillcolor="#C8E6C9"];
|
||||||
compose_ec2 [label="Docker Compose\n(All Services)", fillcolor="#A5D6A7"];
|
|
||||||
nginx [label="Nginx\n(SSL Termination)", fillcolor="#81C784"];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
subgraph cluster_lambda {
|
// AWS Edge
|
||||||
label="Lambda (Data Processing)";
|
subgraph cluster_aws {
|
||||||
|
label="AWS (sysmonstm.mcrn.ar)";
|
||||||
style=filled;
|
style=filled;
|
||||||
fillcolor="#DCEDC8";
|
fillcolor="#F3E5F5";
|
||||||
|
|
||||||
lambda_agg [label="Aggregator\nLambda", fillcolor="#AED581"];
|
edge_relay [label="Edge\n(WebSocket Relay)", fillcolor="#CE93D8"];
|
||||||
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
|
// CI/CD
|
||||||
subgraph cluster_cicd {
|
subgraph cluster_cicd {
|
||||||
label="CI/CD";
|
label="CI/CD";
|
||||||
style=filled;
|
style=filled;
|
||||||
fillcolor="#F3E5F5";
|
fillcolor="#E3F2FD";
|
||||||
|
|
||||||
woodpecker [label="Woodpecker CI", fillcolor="#CE93D8"];
|
woodpecker [label="Woodpecker CI", fillcolor="#90CAF9"];
|
||||||
registry [label="Container\nRegistry", shape=cylinder, fillcolor="#BA68C8"];
|
registry [label="Container\nRegistry", shape=cylinder, fillcolor="#64B5F6"];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collectors (External)
|
// Collectors (External)
|
||||||
@@ -78,18 +54,22 @@ digraph Deployment {
|
|||||||
coll3 [label="Collector\n(Machine N)", fillcolor="#FFCCBC"];
|
coll3 [label="Collector\n(Machine N)", fillcolor="#FFCCBC"];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Browser
|
||||||
|
browser [label="Browser\n(Dashboard)", fillcolor="#FFF3E0"];
|
||||||
|
|
||||||
// Connections
|
// Connections
|
||||||
tilt -> k8s_local [style=invis];
|
coll1 -> aggregator [label="gRPC"];
|
||||||
|
coll2 -> aggregator [label="gRPC"];
|
||||||
|
coll3 -> aggregator [label="gRPC"];
|
||||||
|
|
||||||
|
aggregator -> redis [label="State"];
|
||||||
|
aggregator -> timescaledb [label="Store"];
|
||||||
|
gateway -> aggregator [label="gRPC"];
|
||||||
|
gateway -> edge_relay [label="WebSocket\nForward"];
|
||||||
|
|
||||||
|
edge_relay -> browser [label="WebSocket", dir=both];
|
||||||
|
|
||||||
woodpecker -> registry [label="Push"];
|
woodpecker -> registry [label="Push"];
|
||||||
registry -> compose_ec2 [label="Pull"];
|
registry -> edge_relay [label="Pull", style=dashed];
|
||||||
registry -> k8s_local [label="Pull", style=dashed];
|
registry -> aggregator [label="Pull", style=dashed, lhead=cluster_local];
|
||||||
|
|
||||||
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];
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,221 +1,197 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
<!-- Generated by graphviz version 14.1.1 (0)
|
<!-- Generated by graphviz version 14.1.2 (0)
|
||||||
-->
|
-->
|
||||||
<!-- Title: Deployment Pages: 1 -->
|
<!-- Title: Deployment Pages: 1 -->
|
||||||
<svg width="872pt" height="662pt"
|
<svg width="743pt" height="439pt"
|
||||||
viewBox="0.00 0.00 872.00 662.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
viewBox="0.00 0.00 743.00 439.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)">
|
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 435.03)">
|
||||||
<title>Deployment</title>
|
<title>Deployment</title>
|
||||||
<polygon fill="white" stroke="none" points="-4,4 -4,-658.3 868,-658.3 868,4 -4,4"/>
|
<polygon fill="white" stroke="none" points="-4,4 -4,-435.03 739,-435.03 739,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>
|
<text xml:space="preserve" text-anchor="middle" x="367.5" y="-413.73" font-family="Helvetica,sans-Serif" font-size="14.00">Deployment Architecture</text>
|
||||||
<g id="clust1" class="cluster">
|
<g id="clust1" class="cluster">
|
||||||
<title>cluster_local</title>
|
<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"/>
|
<polygon fill="#e8f5e9" stroke="black" points="292,-8 292,-291.28 518,-291.28 518,-8 292,-8"/>
|
||||||
<text xml:space="preserve" text-anchor="middle" x="123" y="-497.25" font-family="Helvetica,sans-Serif" font-size="14.00">Local Development</text>
|
<text xml:space="preserve" text-anchor="middle" x="405" y="-273.98" font-family="Helvetica,sans-Serif" font-size="14.00">Local Stack (Docker Compose)</text>
|
||||||
</g>
|
</g>
|
||||||
<g id="clust2" class="cluster">
|
<g id="clust2" class="cluster">
|
||||||
<title>cluster_kind</title>
|
<title>cluster_aws</title>
|
||||||
<polygon fill="#bbdefb" stroke="black" points="16,-315.77 16,-481.3 124,-481.3 124,-315.77 16,-315.77"/>
|
<polygon fill="#f3e5f5" stroke="black" points="526,-91.25 526,-168.5 727,-168.5 727,-91.25 526,-91.25"/>
|
||||||
<text xml:space="preserve" text-anchor="middle" x="70" y="-464" font-family="Helvetica,sans-Serif" font-size="14.00">Kind Cluster</text>
|
<text xml:space="preserve" text-anchor="middle" x="626.5" y="-151.2" font-family="Helvetica,sans-Serif" font-size="14.00">AWS (sysmonstm.mcrn.ar)</text>
|
||||||
</g>
|
</g>
|
||||||
<g id="clust3" class="cluster">
|
<g id="clust3" class="cluster">
|
||||||
<title>cluster_aws</title>
|
<title>cluster_cicd</title>
|
||||||
<polygon fill="#e8f5e9" stroke="black" points="642,-8 642,-514.55 856,-514.55 856,-8 642,-8"/>
|
<polygon fill="#e3f2fd" stroke="black" points="531,-209 531,-397.78 635,-397.78 635,-209 531,-209"/>
|
||||||
<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>
|
<text xml:space="preserve" text-anchor="middle" x="583" y="-380.48" font-family="Helvetica,sans-Serif" font-size="14.00">CI/CD</text>
|
||||||
</g>
|
</g>
|
||||||
<g id="clust4" class="cluster">
|
<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>
|
<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"/>
|
<polygon fill="none" stroke="gray" stroke-dasharray="5,2" points="8,-214.03 8,-291.28 284,-291.28 284,-214.03 8,-214.03"/>
|
||||||
<text xml:space="preserve" text-anchor="middle" x="496" y="-464" font-family="Helvetica,sans-Serif" font-size="14.00">Monitored Machines</text>
|
<text xml:space="preserve" text-anchor="middle" x="146" y="-273.98" font-family="Helvetica,sans-Serif" font-size="14.00">Monitored Machines</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- tilt -->
|
<!-- aggregator -->
|
||||||
<g id="node1" class="node">
|
<g id="node1" class="node">
|
||||||
<title>tilt</title>
|
<title>aggregator</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"/>
|
<path fill="#a5d6a7" stroke="black" d="M371.75,-135.25C371.75,-135.25 312.25,-135.25 312.25,-135.25 306.25,-135.25 300.25,-129.25 300.25,-123.25 300.25,-123.25 300.25,-111.25 300.25,-111.25 300.25,-105.25 306.25,-99.25 312.25,-99.25 312.25,-99.25 371.75,-99.25 371.75,-99.25 377.75,-99.25 383.75,-105.25 383.75,-111.25 383.75,-111.25 383.75,-123.25 383.75,-123.25 383.75,-129.25 377.75,-135.25 371.75,-135.25"/>
|
||||||
<polyline fill="none" stroke="black" points="29.75,-444.05 33.75,-444.05 33.75,-440.05 29.75,-440.05"/>
|
<text xml:space="preserve" text-anchor="middle" x="342" y="-120.5" font-family="Helvetica,sans-Serif" font-size="10.00">Aggregator</text>
|
||||||
<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="342" y="-107.75" font-family="Helvetica,sans-Serif" font-size="10.00">(gRPC Server)</text>
|
||||||
<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>
|
</g>
|
||||||
<!-- k8s_local -->
|
<!-- redis -->
|
||||||
<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">
|
<g id="node4" class="node">
|
||||||
<title>compose_ec2</title>
|
<title>redis</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"/>
|
<path fill="#c8e6c9" stroke="black" d="M361,-48.73C361,-50.53 348.9,-52 334,-52 319.1,-52 307,-50.53 307,-48.73 307,-48.73 307,-19.27 307,-19.27 307,-17.47 319.1,-16 334,-16 348.9,-16 361,-17.47 361,-19.27 361,-19.27 361,-48.73 361,-48.73"/>
|
||||||
<text xml:space="preserve" text-anchor="middle" x="707" y="-345.02" font-family="Helvetica,sans-Serif" font-size="10.00">Docker Compose</text>
|
<path fill="none" stroke="black" d="M361,-48.73C361,-46.92 348.9,-45.45 334,-45.45 319.1,-45.45 307,-46.92 307,-48.73"/>
|
||||||
<text xml:space="preserve" text-anchor="middle" x="707" y="-332.27" font-family="Helvetica,sans-Serif" font-size="10.00">(All Services)</text>
|
<text xml:space="preserve" text-anchor="middle" x="334" y="-30.88" font-family="Helvetica,sans-Serif" font-size="10.00">Redis</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- sqs -->
|
<!-- aggregator->redis -->
|
||||||
<g id="node8" class="node">
|
<g id="edge4" class="edge">
|
||||||
<title>sqs</title>
|
<title>aggregator->redis</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"/>
|
<path fill="none" stroke="black" d="M340.3,-99.02C339.29,-88.75 337.98,-75.45 336.82,-63.64"/>
|
||||||
<text xml:space="preserve" text-anchor="middle" x="707" y="-246.42" font-family="Helvetica,sans-Serif" font-size="10.00">SQS</text>
|
<polygon fill="black" stroke="black" points="340.33,-63.6 335.87,-53.99 333.37,-64.29 340.33,-63.6"/>
|
||||||
<text xml:space="preserve" text-anchor="middle" x="707" y="-233.67" font-family="Helvetica,sans-Serif" font-size="10.00">(Buffer)</text>
|
<text xml:space="preserve" text-anchor="middle" x="350.48" y="-72.7" font-family="Helvetica,sans-Serif" font-size="9.00">State</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- compose_ec2->sqs -->
|
<!-- timescaledb -->
|
||||||
<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">
|
<g id="node5" class="node">
|
||||||
<title>nginx</title>
|
<title>timescaledb</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"/>
|
<path fill="#c8e6c9" stroke="black" d="M459.25,-48.73C459.25,-50.53 441.21,-52 419,-52 396.79,-52 378.75,-50.53 378.75,-48.73 378.75,-48.73 378.75,-19.27 378.75,-19.27 378.75,-17.47 396.79,-16 419,-16 441.21,-16 459.25,-17.47 459.25,-19.27 459.25,-19.27 459.25,-48.73 459.25,-48.73"/>
|
||||||
<text xml:space="preserve" text-anchor="middle" x="709" y="-433.3" font-family="Helvetica,sans-Serif" font-size="10.00">Nginx</text>
|
<path fill="none" stroke="black" d="M459.25,-48.73C459.25,-46.92 441.21,-45.45 419,-45.45 396.79,-45.45 378.75,-46.92 378.75,-48.73"/>
|
||||||
<text xml:space="preserve" text-anchor="middle" x="709" y="-420.55" font-family="Helvetica,sans-Serif" font-size="10.00">(SSL Termination)</text>
|
<text xml:space="preserve" text-anchor="middle" x="419" y="-30.88" font-family="Helvetica,sans-Serif" font-size="10.00">TimescaleDB</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- nginx->compose_ec2 -->
|
<!-- aggregator->timescaledb -->
|
||||||
<g id="edge5" class="edge">
|
<g id="edge5" class="edge">
|
||||||
<title>nginx->compose_ec2</title>
|
<title>aggregator->timescaledb</title>
|
||||||
<path fill="none" stroke="black" d="M708.6,-411.59C708.33,-400.13 707.98,-384.86 707.67,-371.63"/>
|
<path fill="none" stroke="black" d="M358.33,-99.02C368.88,-87.89 382.79,-73.21 394.63,-60.72"/>
|
||||||
<polygon fill="black" stroke="black" points="711.17,-371.63 707.44,-361.72 704.17,-371.79 711.17,-371.63"/>
|
<polygon fill="black" stroke="black" points="397.05,-63.25 401.39,-53.58 391.97,-58.44 397.05,-63.25"/>
|
||||||
<text xml:space="preserve" text-anchor="middle" x="720.43" y="-380.47" font-family="Helvetica,sans-Serif" font-size="9.00">Proxy</text>
|
<text xml:space="preserve" text-anchor="middle" x="397.11" y="-72.7" font-family="Helvetica,sans-Serif" font-size="9.00">Store</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- lambda_agg -->
|
<!-- gateway -->
|
||||||
|
<g id="node2" class="node">
|
||||||
|
<title>gateway</title>
|
||||||
|
<path fill="#a5d6a7" stroke="black" d="M383.75,-258.03C383.75,-258.03 348.25,-258.03 348.25,-258.03 342.25,-258.03 336.25,-252.03 336.25,-246.03 336.25,-246.03 336.25,-234.03 336.25,-234.03 336.25,-228.03 342.25,-222.03 348.25,-222.03 348.25,-222.03 383.75,-222.03 383.75,-222.03 389.75,-222.03 395.75,-228.03 395.75,-234.03 395.75,-234.03 395.75,-246.03 395.75,-246.03 395.75,-252.03 389.75,-258.03 383.75,-258.03"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="366" y="-243.28" font-family="Helvetica,sans-Serif" font-size="10.00">Gateway</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="366" y="-230.53" font-family="Helvetica,sans-Serif" font-size="10.00">(FastAPI)</text>
|
||||||
|
</g>
|
||||||
|
<!-- gateway->aggregator -->
|
||||||
|
<g id="edge6" class="edge">
|
||||||
|
<title>gateway->aggregator</title>
|
||||||
|
<path fill="none" stroke="black" d="M362.56,-221.73C358.68,-202.17 352.29,-170.05 347.67,-146.77"/>
|
||||||
|
<polygon fill="black" stroke="black" points="351.11,-146.13 345.73,-137.01 344.24,-147.5 351.11,-146.13"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="369.18" y="-184.82" font-family="Helvetica,sans-Serif" font-size="9.00">gRPC</text>
|
||||||
|
</g>
|
||||||
|
<!-- edge_relay -->
|
||||||
<g id="node6" class="node">
|
<g id="node6" class="node">
|
||||||
<title>lambda_agg</title>
|
<title>edge_relay</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"/>
|
<path fill="#ce93d8" stroke="black" d="M629.75,-135.25C629.75,-135.25 546.25,-135.25 546.25,-135.25 540.25,-135.25 534.25,-129.25 534.25,-123.25 534.25,-123.25 534.25,-111.25 534.25,-111.25 534.25,-105.25 540.25,-99.25 546.25,-99.25 546.25,-99.25 629.75,-99.25 629.75,-99.25 635.75,-99.25 641.75,-105.25 641.75,-111.25 641.75,-111.25 641.75,-123.25 641.75,-123.25 641.75,-129.25 635.75,-135.25 629.75,-135.25"/>
|
||||||
<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="588" y="-120.5" font-family="Helvetica,sans-Serif" font-size="10.00">Edge</text>
|
||||||
<text xml:space="preserve" text-anchor="middle" x="707" y="-117.81" font-family="Helvetica,sans-Serif" font-size="10.00">Lambda</text>
|
<text xml:space="preserve" text-anchor="middle" x="588" y="-107.75" font-family="Helvetica,sans-Serif" font-size="10.00">(WebSocket Relay)</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- lambda_compact -->
|
<!-- gateway->edge_relay -->
|
||||||
<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">
|
<g id="edge7" class="edge">
|
||||||
<title>sqs->lambda_agg</title>
|
<title>gateway->edge_relay</title>
|
||||||
<path fill="none" stroke="black" d="M707,-215.47C707,-197.96 707,-175.06 707,-157.13"/>
|
<path fill="none" stroke="black" d="M384.8,-221.53C401.73,-206.88 428,-186.76 454.75,-176.5 482.85,-165.73 494.1,-179.79 522,-168.5 536.54,-162.62 550.65,-152.65 562.07,-143.13"/>
|
||||||
<polygon fill="black" stroke="black" points="710.5,-157.15 707,-147.15 703.5,-157.15 710.5,-157.15"/>
|
<polygon fill="black" stroke="black" points="564.23,-145.89 569.46,-136.68 559.62,-140.62 564.23,-145.89"/>
|
||||||
<text xml:space="preserve" text-anchor="middle" x="722.75" y="-189.26" font-family="Helvetica,sans-Serif" font-size="9.00">Trigger</text>
|
<text xml:space="preserve" text-anchor="middle" x="479.88" y="-190.45" font-family="Helvetica,sans-Serif" font-size="9.00">WebSocket</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="479.88" y="-179.2" font-family="Helvetica,sans-Serif" font-size="9.00">Forward</text>
|
||||||
|
</g>
|
||||||
|
<!-- alerts -->
|
||||||
|
<g id="node3" class="node">
|
||||||
|
<title>alerts</title>
|
||||||
|
<path fill="#a5d6a7" stroke="black" d="M489,-258.03C489,-258.03 459,-258.03 459,-258.03 453,-258.03 447,-252.03 447,-246.03 447,-246.03 447,-234.03 447,-234.03 447,-228.03 453,-222.03 459,-222.03 459,-222.03 489,-222.03 489,-222.03 495,-222.03 501,-228.03 501,-234.03 501,-234.03 501,-246.03 501,-246.03 501,-252.03 495,-258.03 489,-258.03"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="474" y="-243.28" font-family="Helvetica,sans-Serif" font-size="10.00">Alerts</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="474" y="-230.53" font-family="Helvetica,sans-Serif" font-size="10.00">Service</text>
|
||||||
|
</g>
|
||||||
|
<!-- browser -->
|
||||||
|
<g id="node12" class="node">
|
||||||
|
<title>browser</title>
|
||||||
|
<path fill="#fff3e0" stroke="black" d="M614,-52C614,-52 562,-52 562,-52 556,-52 550,-46 550,-40 550,-40 550,-28 550,-28 550,-22 556,-16 562,-16 562,-16 614,-16 614,-16 620,-16 626,-22 626,-28 626,-28 626,-40 626,-40 626,-46 620,-52 614,-52"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="588" y="-37.25" font-family="Helvetica,sans-Serif" font-size="10.00">Browser</text>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="588" y="-24.5" font-family="Helvetica,sans-Serif" font-size="10.00">(Dashboard)</text>
|
||||||
|
</g>
|
||||||
|
<!-- edge_relay->browser -->
|
||||||
|
<g id="edge8" class="edge">
|
||||||
|
<title>edge_relay->browser</title>
|
||||||
|
<path fill="none" stroke="black" d="M588,-87.54C588,-79.86 588,-71.56 588,-63.88"/>
|
||||||
|
<polygon fill="black" stroke="black" points="584.5,-87.51 588,-97.51 591.5,-87.51 584.5,-87.51"/>
|
||||||
|
<polygon fill="black" stroke="black" points="591.5,-64 588,-54 584.5,-64 591.5,-64"/>
|
||||||
|
<text xml:space="preserve" text-anchor="middle" x="613.12" y="-72.7" font-family="Helvetica,sans-Serif" font-size="9.00">WebSocket</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- woodpecker -->
|
<!-- woodpecker -->
|
||||||
<g id="node10" class="node">
|
<g id="node7" class="node">
|
||||||
<title>woodpecker</title>
|
<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"/>
|
<path fill="#90caf9" stroke="black" d="M615,-364.53C615,-364.53 551,-364.53 551,-364.53 545,-364.53 539,-358.53 539,-352.53 539,-352.53 539,-340.53 539,-340.53 539,-334.53 545,-328.53 551,-328.53 551,-328.53 615,-328.53 615,-328.53 621,-328.53 627,-334.53 627,-340.53 627,-340.53 627,-352.53 627,-352.53 627,-358.53 621,-364.53 615,-364.53"/>
|
||||||
<text xml:space="preserve" text-anchor="middle" x="298" y="-566.67" font-family="Helvetica,sans-Serif" font-size="10.00">Woodpecker CI</text>
|
<text xml:space="preserve" text-anchor="middle" x="583" y="-343.41" font-family="Helvetica,sans-Serif" font-size="10.00">Woodpecker CI</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- registry -->
|
<!-- registry -->
|
||||||
<g id="node11" class="node">
|
<g id="node8" class="node">
|
||||||
<title>registry</title>
|
<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="#64b5f6" stroke="black" d="M614.62,-258.88C614.62,-261.19 600.45,-263.06 583,-263.06 565.55,-263.06 551.38,-261.19 551.38,-258.88 551.38,-258.88 551.38,-221.19 551.38,-221.19 551.38,-218.88 565.55,-217 583,-217 600.45,-217 614.62,-218.88 614.62,-221.19 614.62,-221.19 614.62,-258.88 614.62,-258.88"/>
|
||||||
<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"/>
|
<path fill="none" stroke="black" d="M614.62,-258.88C614.62,-256.56 600.45,-254.69 583,-254.69 565.55,-254.69 551.38,-256.56 551.38,-258.88"/>
|
||||||
<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="583" y="-243.28" 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>
|
<text xml:space="preserve" text-anchor="middle" x="583" y="-230.53" font-family="Helvetica,sans-Serif" font-size="10.00">Registry</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- woodpecker->registry -->
|
<!-- woodpecker->registry -->
|
||||||
<g id="edge2" class="edge">
|
<g id="edge9" class="edge">
|
||||||
<title>woodpecker->registry</title>
|
<title>woodpecker->registry</title>
|
||||||
<path fill="none" stroke="black" d="M298,-551.35C298,-529.66 298,-492.15 298,-464.77"/>
|
<path fill="none" stroke="black" d="M583,-328.28C583,-313.79 583,-292.67 583,-274.84"/>
|
||||||
<polygon fill="black" stroke="black" points="301.5,-464.88 298,-454.88 294.5,-464.88 301.5,-464.88"/>
|
<polygon fill="black" stroke="black" points="586.5,-274.93 583,-264.93 579.5,-274.93 586.5,-274.93"/>
|
||||||
<text xml:space="preserve" text-anchor="middle" x="308.88" y="-525.25" font-family="Helvetica,sans-Serif" font-size="9.00">Push</text>
|
<text xml:space="preserve" text-anchor="middle" x="593.88" y="-301.98" font-family="Helvetica,sans-Serif" font-size="9.00">Push</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- registry->k8s_local -->
|
<!-- registry->aggregator -->
|
||||||
<g id="edge4" class="edge">
|
<g id="edge11" class="edge">
|
||||||
<title>registry->k8s_local</title>
|
<title>registry->aggregator</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"/>
|
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M560.24,-217.39C550.98,-209.15 539.58,-199.63 527.42,-190.91"/>
|
||||||
<polygon fill="black" stroke="black" points="128.47,-355.44 117.89,-355.97 126.49,-362.15 128.47,-355.44"/>
|
<polygon fill="black" stroke="black" points="529.47,-188.06 519.24,-185.28 525.5,-193.83 529.47,-188.06"/>
|
||||||
<text xml:space="preserve" text-anchor="middle" x="222.42" y="-380.47" font-family="Helvetica,sans-Serif" font-size="9.00">Pull</text>
|
<text xml:space="preserve" text-anchor="middle" x="544.69" y="-184.82" font-family="Helvetica,sans-Serif" font-size="9.00">Pull</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- registry->compose_ec2 -->
|
<!-- registry->edge_relay -->
|
||||||
<g id="edge3" class="edge">
|
<g id="edge10" class="edge">
|
||||||
<title>registry->compose_ec2</title>
|
<title>registry->edge_relay</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"/>
|
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M583.93,-216.6C584.74,-196.89 585.94,-168.11 586.82,-146.77"/>
|
||||||
<polygon fill="black" stroke="black" points="646.02,-349.01 655.78,-344.88 645.58,-342.02 646.02,-349.01"/>
|
<polygon fill="black" stroke="black" points="590.31,-147.14 587.22,-137 583.31,-146.85 590.31,-147.14"/>
|
||||||
<text xml:space="preserve" text-anchor="middle" x="427.09" y="-380.47" font-family="Helvetica,sans-Serif" font-size="9.00">Pull</text>
|
<text xml:space="preserve" text-anchor="middle" x="593.38" y="-184.82" font-family="Helvetica,sans-Serif" font-size="9.00">Pull</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- coll1 -->
|
<!-- coll1 -->
|
||||||
<g id="node12" class="node">
|
<g id="node9" class="node">
|
||||||
<title>coll1</title>
|
<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"/>
|
<path fill="#ffccbc" stroke="black" d="M263.88,-258.03C263.88,-258.03 214.12,-258.03 214.12,-258.03 208.12,-258.03 202.12,-252.03 202.12,-246.03 202.12,-246.03 202.12,-234.03 202.12,-234.03 202.12,-228.03 208.12,-222.03 214.12,-222.03 214.12,-222.03 263.88,-222.03 263.88,-222.03 269.88,-222.03 275.88,-228.03 275.88,-234.03 275.88,-234.03 275.88,-246.03 275.88,-246.03 275.88,-252.03 269.88,-258.03 263.88,-258.03"/>
|
||||||
<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="239" y="-243.28" 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>
|
<text xml:space="preserve" text-anchor="middle" x="239" y="-230.53" font-family="Helvetica,sans-Serif" font-size="10.00">(Machine 1)</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- coll1->compose_ec2 -->
|
<!-- coll1->aggregator -->
|
||||||
<g id="edge9" class="edge">
|
<g id="edge1" class="edge">
|
||||||
<title>coll1->compose_ec2</title>
|
<title>coll1->aggregator</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"/>
|
<path fill="none" stroke="black" d="M253.76,-221.73C271.04,-201.46 299.85,-167.67 319.83,-144.25"/>
|
||||||
<polygon fill="black" stroke="black" points="640.37,-365.52 648.58,-358.82 637.98,-358.94 640.37,-365.52"/>
|
<polygon fill="black" stroke="black" points="322.46,-146.55 326.29,-136.67 317.14,-142.01 322.46,-146.55"/>
|
||||||
<text xml:space="preserve" text-anchor="middle" x="602.75" y="-380.47" font-family="Helvetica,sans-Serif" font-size="9.00">gRPC</text>
|
<text xml:space="preserve" text-anchor="middle" x="302.12" y="-184.82" font-family="Helvetica,sans-Serif" font-size="9.00">gRPC</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- coll2 -->
|
<!-- coll2 -->
|
||||||
<g id="node13" class="node">
|
<g id="node10" class="node">
|
||||||
<title>coll2</title>
|
<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"/>
|
<path fill="#ffccbc" stroke="black" d="M77.88,-258.03C77.88,-258.03 28.12,-258.03 28.12,-258.03 22.12,-258.03 16.12,-252.03 16.12,-246.03 16.12,-246.03 16.12,-234.03 16.12,-234.03 16.12,-228.03 22.12,-222.03 28.12,-222.03 28.12,-222.03 77.88,-222.03 77.88,-222.03 83.88,-222.03 89.88,-228.03 89.88,-234.03 89.88,-234.03 89.88,-246.03 89.88,-246.03 89.88,-252.03 83.88,-258.03 77.88,-258.03"/>
|
||||||
<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="53" y="-243.28" 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>
|
<text xml:space="preserve" text-anchor="middle" x="53" y="-230.53" font-family="Helvetica,sans-Serif" font-size="10.00">(Machine 2)</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- coll2->compose_ec2 -->
|
<!-- coll2->aggregator -->
|
||||||
<g id="edge10" class="edge">
|
<g id="edge2" class="edge">
|
||||||
<title>coll2->compose_ec2</title>
|
<title>coll2->aggregator</title>
|
||||||
<path fill="none" stroke="black" d="M612.88,-411.59C621.13,-405.55 630.83,-398.47 640.8,-391.17"/>
|
<path fill="none" stroke="black" d="M77.23,-221.79C84.09,-217.31 91.68,-212.7 99,-209 161.95,-177.15 238.85,-150.3 289.05,-134.25"/>
|
||||||
<polygon fill="black" stroke="black" points="642.77,-394.07 648.78,-385.34 638.64,-388.41 642.77,-394.07"/>
|
<polygon fill="black" stroke="black" points="290.05,-137.61 298.53,-131.26 287.94,-130.94 290.05,-137.61"/>
|
||||||
<text xml:space="preserve" text-anchor="middle" x="670.19" y="-380.47" font-family="Helvetica,sans-Serif" font-size="9.00">gRPC</text>
|
<text xml:space="preserve" text-anchor="middle" x="182.04" y="-184.82" font-family="Helvetica,sans-Serif" font-size="9.00">gRPC</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- coll3 -->
|
<!-- coll3 -->
|
||||||
<g id="node14" class="node">
|
<g id="node11" class="node">
|
||||||
<title>coll3</title>
|
<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"/>
|
<path fill="#ffccbc" stroke="black" d="M171.62,-258.03C171.62,-258.03 120.38,-258.03 120.38,-258.03 114.38,-258.03 108.38,-252.03 108.38,-246.03 108.38,-246.03 108.38,-234.03 108.38,-234.03 108.38,-228.03 114.38,-222.03 120.38,-222.03 120.38,-222.03 171.62,-222.03 171.62,-222.03 177.62,-222.03 183.62,-228.03 183.62,-234.03 183.62,-234.03 183.62,-246.03 183.62,-246.03 183.62,-252.03 177.62,-258.03 171.62,-258.03"/>
|
||||||
<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="146" y="-243.28" 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>
|
<text xml:space="preserve" text-anchor="middle" x="146" y="-230.53" font-family="Helvetica,sans-Serif" font-size="10.00">(Machine N)</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- coll3->compose_ec2 -->
|
<!-- coll3->aggregator -->
|
||||||
<g id="edge11" class="edge">
|
<g id="edge3" class="edge">
|
||||||
<title>coll3->compose_ec2</title>
|
<title>coll3->aggregator</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"/>
|
<path fill="none" stroke="black" d="M172.78,-221.78C179.37,-217.57 186.43,-213.1 193,-209 230.29,-185.74 273.17,-159.69 303.33,-141.49"/>
|
||||||
<polygon fill="black" stroke="black" points="639.16,-354.39 648.5,-349.4 638.08,-347.48 639.16,-354.39"/>
|
<polygon fill="black" stroke="black" points="304.99,-144.58 311.75,-136.42 301.38,-138.58 304.99,-144.58"/>
|
||||||
<text xml:space="preserve" text-anchor="middle" x="516.54" y="-380.47" font-family="Helvetica,sans-Serif" font-size="9.00">gRPC</text>
|
<text xml:space="preserve" text-anchor="middle" x="253.61" y="-184.82" font-family="Helvetica,sans-Serif" font-size="9.00">gRPC</text>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 15 KiB |
@@ -86,8 +86,7 @@ main {
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
overflow: auto;
|
overflow: visible;
|
||||||
max-height: 400px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.graph-preview img {
|
.graph-preview img {
|
||||||
|
|||||||
@@ -231,6 +231,31 @@ machine_metrics_cache[machine_id].update(incoming_metrics)
|
|||||||
|
|
||||||
New metrics merge with existing. The broadcast includes the full merged state.
|
New metrics merge with existing. The broadcast includes the full merged state.
|
||||||
|
|
||||||
|
### Edge Relay - Public Dashboard Without the Cost
|
||||||
|
|
||||||
|
The full stack (aggregator, Redis, TimescaleDB) runs on local hardware. But the dashboard needs to be publicly accessible at `sysmonstm.mcrn.ar`. Running the full stack on AWS would be expensive and unnecessary.
|
||||||
|
|
||||||
|
The solution is an edge relay (`ctrl/edge/edge.py`). It's a minimal FastAPI app that does one thing: relay WebSocket messages. The gateway forwards metrics to the edge via WebSocket, and the edge broadcasts them to connected browsers:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Gateway forwards to edge when EDGE_URL is configured
|
||||||
|
async def forward_to_edge(data: dict):
|
||||||
|
if edge_ws:
|
||||||
|
await edge_ws.send(json.dumps(data))
|
||||||
|
```
|
||||||
|
|
||||||
|
The edge receives these and broadcasts to all dashboard viewers:
|
||||||
|
|
||||||
|
```python
|
||||||
|
@app.websocket("/ws")
|
||||||
|
async def dashboard_ws(websocket: WebSocket):
|
||||||
|
await websocket.accept()
|
||||||
|
clients.add(websocket)
|
||||||
|
# ... broadcasts incoming metrics to all clients
|
||||||
|
```
|
||||||
|
|
||||||
|
This keeps heavy processing (gRPC, storage, event evaluation) on local hardware and puts only a lightweight relay in the cloud. The AWS instance has no databases, no gRPC, no storage — just WebSocket in, WebSocket out.
|
||||||
|
|
||||||
## Phase 3: Alerts - Adding Intelligence
|
## Phase 3: Alerts - Adding Intelligence
|
||||||
|
|
||||||
The alerts service subscribes to metric events and evaluates them against rules.
|
The alerts service subscribes to metric events and evaluates them against rules.
|
||||||
@@ -402,7 +427,8 @@ Set `COLLECTOR_AGGREGATOR_URL=192.168.1.100:50051` and it overrides the default.
|
|||||||
| Redis events | `shared/events/redis_pubsub.py` | Redis Pub/Sub implementation |
|
| Redis events | `shared/events/redis_pubsub.py` | Redis Pub/Sub implementation |
|
||||||
| Configuration | `shared/config.py` | Pydantic settings for all services |
|
| Configuration | `shared/config.py` | Pydantic settings for all services |
|
||||||
| DB initialization | `scripts/init-db.sql` | TimescaleDB schema, hypertables |
|
| DB initialization | `scripts/init-db.sql` | TimescaleDB schema, hypertables |
|
||||||
| Docker setup | `docker-compose.yml` | Full stack orchestration |
|
| Edge relay | `ctrl/edge/edge.py` | WebSocket relay for AWS dashboard |
|
||||||
|
| Docker setup | `ctrl/dev/docker-compose.yml` | Full stack orchestration |
|
||||||
|
|
||||||
## Running It
|
## Running It
|
||||||
|
|
||||||
|
|||||||
@@ -80,39 +80,6 @@
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main>
|
<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 -->
|
<!-- Architecture Diagrams -->
|
||||||
<section class="graph-section" id="overview">
|
<section class="graph-section" id="overview">
|
||||||
<div class="graph-header-row">
|
<div class="graph-header-row">
|
||||||
@@ -155,6 +122,11 @@
|
|||||||
<strong>Alerts</strong>: Subscribes to events,
|
<strong>Alerts</strong>: Subscribes to events,
|
||||||
evaluates thresholds, triggers actions
|
evaluates thresholds, triggers actions
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Edge</strong>: Lightweight WebSocket
|
||||||
|
relay on AWS, serves public dashboard at
|
||||||
|
sysmonstm.mcrn.ar
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -245,16 +217,17 @@
|
|||||||
<h4>Environments</h4>
|
<h4>Environments</h4>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<strong>Local Dev</strong>: Kind + Tilt for K8s, or
|
<strong>Local</strong>: Docker Compose with
|
||||||
Docker Compose
|
aggregator, gateway, Redis, TimescaleDB, alerts
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<strong>Demo (EC2)</strong>: Docker Compose on
|
<strong>Edge (AWS)</strong>: Lightweight
|
||||||
t2.small at sysmonstm.mcrn.ar
|
WebSocket relay at sysmonstm.mcrn.ar, receives
|
||||||
|
forwarded metrics from local gateway
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<strong>Lambda Pipeline</strong>: SQS-triggered
|
<strong>Collectors</strong>: Run on remote
|
||||||
aggregation for data processing experience
|
machines, stream to local aggregator via gRPC
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -301,7 +274,7 @@
|
|||||||
<hr class="section-divider" />
|
<hr class="section-divider" />
|
||||||
|
|
||||||
<section class="findings-section">
|
<section class="findings-section">
|
||||||
<h2>Interview Talking Points</h2>
|
<h2>Key Design Decisions</h2>
|
||||||
<div class="findings-grid">
|
<div class="findings-grid">
|
||||||
<article class="finding-card">
|
<article class="finding-card">
|
||||||
<h3>Domain Mapping</h3>
|
<h3>Domain Mapping</h3>
|
||||||
@@ -366,16 +339,13 @@
|
|||||||
<h3>Infrastructure</h3>
|
<h3>Infrastructure</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Docker</li>
|
<li>Docker</li>
|
||||||
<li>Kubernetes</li>
|
<li>Docker Compose</li>
|
||||||
<li>Kind + Tilt</li>
|
|
||||||
<li>Terraform</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="tech-column">
|
<div class="tech-column">
|
||||||
<h3>CI/CD</h3>
|
<h3>CI/CD</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Woodpecker CI</li>
|
<li>Woodpecker CI</li>
|
||||||
<li>Kustomize</li>
|
|
||||||
<li>Container Registry</li>
|
<li>Container Registry</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -386,7 +356,7 @@
|
|||||||
<footer>
|
<footer>
|
||||||
<p>System Monitoring Platform - Documentation</p>
|
<p>System Monitoring Platform - Documentation</p>
|
||||||
<p class="date">
|
<p class="date">
|
||||||
Generated: <time datetime="2025-12-31">December 2025</time>
|
Generated: <time datetime="2026-03-16">March 2026</time>
|
||||||
</p>
|
</p>
|
||||||
</footer>
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ RUN python -m grpc_tools.protoc \
|
|||||||
-I/app/proto \
|
-I/app/proto \
|
||||||
--python_out=/app/shared \
|
--python_out=/app/shared \
|
||||||
--grpc_python_out=/app/shared \
|
--grpc_python_out=/app/shared \
|
||||||
/app/proto/metrics.proto
|
/app/proto/metrics.proto \
|
||||||
|
&& sed -i 's/^import metrics_pb2/from shared import metrics_pb2/' /app/shared/metrics_pb2_grpc.py
|
||||||
|
|
||||||
COPY services/aggregator /app/services/aggregator
|
COPY services/aggregator /app/services/aggregator
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ RUN python -m grpc_tools.protoc \
|
|||||||
-I/app/proto \
|
-I/app/proto \
|
||||||
--python_out=/app/shared \
|
--python_out=/app/shared \
|
||||||
--grpc_python_out=/app/shared \
|
--grpc_python_out=/app/shared \
|
||||||
/app/proto/metrics.proto
|
/app/proto/metrics.proto \
|
||||||
|
&& sed -i 's/^import metrics_pb2/from shared import metrics_pb2/' /app/shared/metrics_pb2_grpc.py
|
||||||
|
|
||||||
COPY services/alerts /app/services/alerts
|
COPY services/alerts /app/services/alerts
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ RUN python -m grpc_tools.protoc \
|
|||||||
-I/app/proto \
|
-I/app/proto \
|
||||||
--python_out=/app/shared \
|
--python_out=/app/shared \
|
||||||
--grpc_python_out=/app/shared \
|
--grpc_python_out=/app/shared \
|
||||||
/app/proto/metrics.proto
|
/app/proto/metrics.proto \
|
||||||
|
&& sed -i 's/^import metrics_pb2/from shared import metrics_pb2/' /app/shared/metrics_pb2_grpc.py
|
||||||
|
|
||||||
# Copy service code
|
# Copy service code
|
||||||
COPY services/collector /app/services/collector
|
COPY services/collector /app/services/collector
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ RUN python -m grpc_tools.protoc \
|
|||||||
-I/app/proto \
|
-I/app/proto \
|
||||||
--python_out=/app/shared \
|
--python_out=/app/shared \
|
||||||
--grpc_python_out=/app/shared \
|
--grpc_python_out=/app/shared \
|
||||||
/app/proto/metrics.proto
|
/app/proto/metrics.proto \
|
||||||
|
&& sed -i 's/^import metrics_pb2/from shared import metrics_pb2/' /app/shared/metrics_pb2_grpc.py
|
||||||
|
|
||||||
COPY services/gateway /app/services/gateway
|
COPY services/gateway /app/services/gateway
|
||||||
COPY services/aggregator/__init__.py /app/services/aggregator/__init__.py
|
COPY services/aggregator/__init__.py /app/services/aggregator/__init__.py
|
||||||
|
|||||||
@@ -32,6 +32,11 @@ logger = setup_logging(
|
|||||||
log_format=config.log_format,
|
log_format=config.log_format,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Edge forwarding config
|
||||||
|
EDGE_URL = config.edge_url if hasattr(config, "edge_url") else None
|
||||||
|
EDGE_API_KEY = config.edge_api_key if hasattr(config, "edge_api_key") else ""
|
||||||
|
edge_ws = None
|
||||||
|
|
||||||
|
|
||||||
# WebSocket connection manager
|
# WebSocket connection manager
|
||||||
class ConnectionManager:
|
class ConnectionManager:
|
||||||
@@ -77,6 +82,54 @@ timescale: TimescaleStorage | None = None
|
|||||||
grpc_channel: grpc.aio.Channel | None = None
|
grpc_channel: grpc.aio.Channel | None = None
|
||||||
grpc_stub: metrics_pb2_grpc.MetricsServiceStub | None = None
|
grpc_stub: metrics_pb2_grpc.MetricsServiceStub | None = None
|
||||||
|
|
||||||
|
|
||||||
|
async def connect_to_edge():
|
||||||
|
"""Maintain persistent WebSocket connection to edge and forward metrics."""
|
||||||
|
global edge_ws
|
||||||
|
|
||||||
|
if not EDGE_URL:
|
||||||
|
logger.info("edge_not_configured", msg="No EDGE_URL set, running local only")
|
||||||
|
return
|
||||||
|
|
||||||
|
import websockets
|
||||||
|
|
||||||
|
url = EDGE_URL
|
||||||
|
if EDGE_API_KEY:
|
||||||
|
separator = "&" if "?" in url else "?"
|
||||||
|
url = f"{url}{separator}key={EDGE_API_KEY}"
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
logger.info("edge_connecting", url=EDGE_URL)
|
||||||
|
async with websockets.connect(url) as ws:
|
||||||
|
edge_ws = ws
|
||||||
|
logger.info("edge_connected")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
msg = await asyncio.wait_for(ws.recv(), timeout=30)
|
||||||
|
# Ignore messages from edge (pings, etc)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
await ws.ping()
|
||||||
|
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
edge_ws = None
|
||||||
|
logger.warning("edge_connection_error", error=str(e))
|
||||||
|
await asyncio.sleep(5)
|
||||||
|
|
||||||
|
|
||||||
|
async def forward_to_edge(data: dict):
|
||||||
|
"""Forward metrics to edge if connected."""
|
||||||
|
global edge_ws
|
||||||
|
if edge_ws:
|
||||||
|
try:
|
||||||
|
await edge_ws.send(json.dumps(data))
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning("edge_forward_error", error=str(e))
|
||||||
|
|
||||||
|
|
||||||
# Track recent events for internals view
|
# Track recent events for internals view
|
||||||
recent_events: list[dict] = []
|
recent_events: list[dict] = []
|
||||||
MAX_RECENT_EVENTS = 100
|
MAX_RECENT_EVENTS = 100
|
||||||
@@ -133,15 +186,17 @@ async def event_listener():
|
|||||||
merged_payload = event.payload
|
merged_payload = event.payload
|
||||||
|
|
||||||
# Broadcast merged data to dashboard
|
# Broadcast merged data to dashboard
|
||||||
await manager.broadcast(
|
broadcast_msg = {
|
||||||
{
|
|
||||||
"type": "metrics",
|
"type": "metrics",
|
||||||
"data": merged_payload,
|
"data": merged_payload,
|
||||||
"timestamp": event.timestamp.isoformat(),
|
"timestamp": event.timestamp.isoformat(),
|
||||||
}
|
}
|
||||||
)
|
await manager.broadcast(broadcast_msg)
|
||||||
service_stats["websocket_broadcasts"] += 1
|
service_stats["websocket_broadcasts"] += 1
|
||||||
|
|
||||||
|
# Forward to edge if connected
|
||||||
|
await forward_to_edge(broadcast_msg)
|
||||||
|
|
||||||
# Broadcast to internals (show raw event, not merged)
|
# Broadcast to internals (show raw event, not merged)
|
||||||
await internals_manager.broadcast(
|
await internals_manager.broadcast(
|
||||||
{
|
{
|
||||||
@@ -176,6 +231,9 @@ async def lifespan(app: FastAPI):
|
|||||||
# Start event listener in background
|
# Start event listener in background
|
||||||
listener_task = asyncio.create_task(event_listener())
|
listener_task = asyncio.create_task(event_listener())
|
||||||
|
|
||||||
|
# Start edge connection if configured
|
||||||
|
edge_task = asyncio.create_task(connect_to_edge())
|
||||||
|
|
||||||
service_stats["started_at"] = datetime.utcnow().isoformat()
|
service_stats["started_at"] = datetime.utcnow().isoformat()
|
||||||
logger.info("gateway_started")
|
logger.info("gateway_started")
|
||||||
|
|
||||||
@@ -183,10 +241,15 @@ async def lifespan(app: FastAPI):
|
|||||||
|
|
||||||
# Cleanup
|
# Cleanup
|
||||||
listener_task.cancel()
|
listener_task.cancel()
|
||||||
|
edge_task.cancel()
|
||||||
try:
|
try:
|
||||||
await listener_task
|
await listener_task
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
pass
|
pass
|
||||||
|
try:
|
||||||
|
await edge_task
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
pass
|
||||||
|
|
||||||
if grpc_channel:
|
if grpc_channel:
|
||||||
await grpc_channel.close()
|
await grpc_channel.close()
|
||||||
|
|||||||
@@ -74,6 +74,10 @@ class GatewayConfig(BaseConfig):
|
|||||||
# TimescaleDB - can be set directly via TIMESCALE_URL
|
# TimescaleDB - can be set directly via TIMESCALE_URL
|
||||||
timescale_url: str = "postgresql://monitor:monitor@localhost:5432/monitor"
|
timescale_url: str = "postgresql://monitor:monitor@localhost:5432/monitor"
|
||||||
|
|
||||||
|
# Edge forwarding (optional - for pushing to cloud edge)
|
||||||
|
edge_url: str = "" # e.g., wss://sysmonstm.mcrn.ar/ws
|
||||||
|
edge_api_key: str = ""
|
||||||
|
|
||||||
|
|
||||||
class AlertsConfig(BaseConfig):
|
class AlertsConfig(BaseConfig):
|
||||||
"""Alerts service configuration."""
|
"""Alerts service configuration."""
|
||||||
|
|||||||
Reference in New Issue
Block a user