diff options
41 files changed, 5920 insertions, 0 deletions
diff --git a/AGENT.md b/AGENT.md new file mode 100644 index 0000000..d5c22e7 --- /dev/null +++ b/AGENT.md @@ -0,0 +1,63 @@ +Follow ~/git/conf/snippets/go/go-projects.md + +## Grafana Dashboard Guidelines + +When creating or updating Grafana dashboards: + +### Sorting Requirements +**ALWAYS ensure ALL panels are sorted by value (descending):** + +1. **Time Series Panels:** + - Add to `options.legend`: `"sortBy": "Last", "sortDesc": true` + +2. **Bar Gauge Panels:** + - Use `sort_desc()` in PromQL queries + - Example: `sort_desc(topk(10, sum by (label) (metric)))` + +3. **Pie/Donut Chart Panels:** + - Add to `options.legend`: `"sortBy": "Value", "sortDesc": true` + +4. **Table Panels:** + - Add to `options`: `"sortBy": [{"displayName": "ColumnName", "desc": true}]` + +### Example Panel Configuration + +**Time Series:** +```json +{ + "type": "timeseries", + "options": { + "legend": { + "displayMode": "table", + "placement": "right", + "sortBy": "Last", + "sortDesc": true, + "calcs": ["lastNotNull", "mean", "max"] + } + } +} +``` + +**Bar Gauge:** +```json +{ + "type": "bargauge", + "targets": [{ + "expr": "sort_desc(topk(15, sum by (cust) (metric)))" + }], + "options": { + "orientation": "horizontal", + "displayMode": "gradient" + } +} +``` + +**Table:** +```json +{ + "type": "table", + "options": { + "sortBy": [{"displayName": "Count", "desc": true}] + } +} +``` diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..02d954c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +Follow AGENT.md diff --git a/Magefile.go b/Magefile.go new file mode 100644 index 0000000..3cce6e0 --- /dev/null +++ b/Magefile.go @@ -0,0 +1,253 @@ +//go:build mage +// +build mage + +package main + +import ( + "fmt" + "os" + + "github.com/magefile/mage/mg" + "github.com/magefile/mage/sh" +) + +const ( + binaryName = "epimetheus" + mainPath = "./cmd/epimetheus" +) + +// Default target to run when none is specified +var Default = Build + +// Build compiles the epimetheus binary +func Build() error { + fmt.Println("Building epimetheus...") + return sh.RunV("go", "build", "-o", binaryName, mainPath) +} + +// Install installs the binary to $GOPATH/bin +func Install() error { + fmt.Println("Installing epimetheus...") + return sh.RunV("go", "install", mainPath) +} + +// Run executes the epimetheus binary in realtime mode +func Run() error { + mg.Deps(Build) + fmt.Println("Running epimetheus in realtime mode...") + return sh.RunV("./"+binaryName, "-mode=realtime", "-continuous") +} + +// RunHistoric runs epimetheus in historic mode +func RunHistoric() error { + mg.Deps(Build) + fmt.Println("Running epimetheus in historic mode (24 hours ago)...") + return sh.RunV("./"+binaryName, "-mode=historic", "-hours-ago=24") +} + +// RunAuto runs epimetheus in auto mode with a file +func RunAuto(file string) error { + mg.Deps(Build) + if file == "" { + return fmt.Errorf("file parameter required: mage RunAuto <file>") + } + fmt.Printf("Running epimetheus in auto mode with file: %s\n", file) + return sh.RunV("./"+binaryName, "-mode=auto", "-file="+file) +} + +// RunWatchClickHouse runs epimetheus in watch mode with ClickHouse ingestion +func RunWatchClickHouse(file string) error { + mg.Deps(Build) + if file == "" { + file = "test-data/watch-clickhouse-test.csv" + } + fmt.Printf("Running epimetheus in watch mode with ClickHouse (file: %s)\n", file) + return sh.RunV("./"+binaryName, "-mode=watch", "-file="+file, "-metric-name=watch_test", + "-clickhouse=http://localhost:8123", "-prometheus=") +} + +// Test runs all tests +func Test() error { + fmt.Println("Running tests...") + return sh.RunV("go", "test", "./...", "-v") +} + +// TestCoverage runs tests with coverage report +func TestCoverage() error { + fmt.Println("Running tests with coverage...") + if err := sh.RunV("go", "test", "./...", "-cover", "-coverprofile=coverage.out"); err != nil { + return err + } + return sh.RunV("go", "tool", "cover", "-html=coverage.out", "-o", "coverage.html") +} + +// TestRace runs tests with race detector +func TestRace() error { + fmt.Println("Running tests with race detector...") + return sh.RunV("go", "test", "./...", "-race", "-v") +} + +// Benchmark runs all benchmarks +func Benchmark() error { + fmt.Println("Running benchmarks...") + return sh.RunV("go", "test", "./...", "-bench=.", "-benchmem") +} + +// Lint runs golangci-lint +func Lint() error { + fmt.Println("Running linter...") + return sh.RunV("golangci-lint", "run", "./...") +} + +// Fmt formats all Go code +func Fmt() error { + fmt.Println("Formatting code...") + return sh.RunV("go", "fmt", "./...") +} + +// Vet runs go vet +func Vet() error { + fmt.Println("Running go vet...") + return sh.RunV("go", "vet", "./...") +} + +// Tidy runs go mod tidy +func Tidy() error { + fmt.Println("Tidying dependencies...") + return sh.RunV("go", "mod", "tidy") +} + +// Clean removes build artifacts +func Clean() error { + fmt.Println("Cleaning build artifacts...") + files := []string{ + binaryName, + "coverage.out", + "coverage.html", + } + for _, f := range files { + if err := sh.Rm(f); err != nil && !os.IsNotExist(err) { + return err + } + } + return nil +} + +// Generate runs go generate +func Generate() error { + fmt.Println("Running go generate...") + return sh.RunV("go", "generate", "./...") +} + +// Version prints the version +func Version() error { + fmt.Println("Printing version...") + mg.Deps(Build) + return sh.RunV("./"+binaryName, "-version") +} + +// All runs format, vet, test, and build +func All() { + mg.Deps(Fmt, Vet, Test, Build) +} + +// CI runs the full CI pipeline (format check, vet, test, build) +func CI() error { + fmt.Println("Running CI pipeline...") + mg.Deps(Tidy, Vet, Test) + return Build() +} + +// Dev starts development mode with port-forwarding +func Dev() error { + mg.Deps(Build) + fmt.Println("Starting development mode...") + fmt.Println("Setting up port-forward to Pushgateway...") + + // Start port-forward in background + portForwardCmd := sh.RunCmd("kubectl", "port-forward", "-n", "monitoring", "svc/pushgateway", "9091:9091") + go func() { + if err := portForwardCmd(); err != nil { + fmt.Printf("Port-forward error: %v\n", err) + } + }() + + fmt.Println("Running epimetheus in realtime mode...") + return sh.RunV("./"+binaryName, "-mode=realtime", "-continuous") +} + +// GenerateTestData creates test data files +func GenerateTestData() error { + fmt.Println("Generating test data...") + return sh.RunV("./generate-test-data.sh") +} + +// Backfill runs backfill for the last 48 hours +func Backfill() error { + mg.Deps(Build) + fmt.Println("Running backfill (last 48 hours)...") + return sh.RunV("./"+binaryName, "-mode=backfill", "-start-hours=48", "-end-hours=0", "-interval=1") +} + +// Benchmark100MB runs the 100MB benchmark +func Benchmark100MB() error { + fmt.Println("Running 100MB benchmark...") + return sh.RunV("./benchmark-100mb.sh") +} + +// Benchmark1GB runs the 1GB benchmark +func Benchmark1GB() error { + fmt.Println("Running 1GB benchmark...") + return sh.RunV("./benchmark-1gb.sh") +} + +// CleanupBenchmarkData removes benchmark data from Prometheus +func CleanupBenchmarkData() error { + fmt.Println("Cleaning up benchmark data...") + return sh.RunV("./cleanup-benchmark-data.sh") +} + +// CleanupBenchmarkMetrics removes benchmark metric files +func CleanupBenchmarkMetrics() error { + fmt.Println("Cleaning up benchmark metric files...") + return sh.RunV("./cleanup-benchmark-metrics.sh") +} + +// DeployDashboard deploys the Grafana dashboard +func DeployDashboard() error { + fmt.Println("Deploying Grafana dashboard...") + return sh.RunV("./deploy-dashboard.sh") +} + +// Help prints available targets +func Help() { + fmt.Println("Available targets:") + fmt.Println(" build - Build the epimetheus binary (default)") + fmt.Println(" install - Install the binary to $GOPATH/bin") + fmt.Println(" run - Build and run in realtime mode") + fmt.Println(" runHistoric - Build and run in historic mode") + fmt.Println(" runAuto <file> - Build and run in auto mode with file") + fmt.Println(" runWatchClickHouse [file] - Build and run watch mode with ClickHouse (default: test-data/watch-clickhouse-test.csv)") + fmt.Println(" test - Run all tests") + fmt.Println(" testCoverage - Run tests with coverage report") + fmt.Println(" testRace - Run tests with race detector") + fmt.Println(" benchmark - Run Go benchmarks") + fmt.Println(" lint - Run golangci-lint") + fmt.Println(" fmt - Format all Go code") + fmt.Println(" vet - Run go vet") + fmt.Println(" tidy - Run go mod tidy") + fmt.Println(" clean - Remove build artifacts") + fmt.Println(" generate - Run go generate") + fmt.Println(" version - Print version") + fmt.Println(" all - Run fmt, vet, test, and build") + fmt.Println(" ci - Run full CI pipeline") + fmt.Println(" dev - Start development mode with port-forwarding") + fmt.Println(" generateTestData - Generate test data files") + fmt.Println(" backfill - Run backfill for last 48 hours") + fmt.Println(" benchmark100MB - Run 100MB benchmark") + fmt.Println(" benchmark1GB - Run 1GB benchmark") + fmt.Println(" cleanupBenchmarkData - Clean up benchmark data from Prometheus") + fmt.Println(" cleanupBenchmarkMetrics - Clean up benchmark metric files") + fmt.Println(" deployDashboard - Deploy Grafana dashboard") + fmt.Println(" help - Print this help message") +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..ba10a76 --- /dev/null +++ b/README.md @@ -0,0 +1,1000 @@ +<div align="center"> + <img src="logo.png" alt="Epimetheus Logo" width="400"/> +</div> + +# Epimetheus + +A versatile Go tool for pushing metrics to Prometheus with support for both realtime and historic data ingestion. + +## Why "Epimetheus"? + +In Greek mythology, [Epimetheus](https://en.wikipedia.org/wiki/Epimetheus_(mythology)) is Prometheus's brother, whose name means "afterthought" or "hindsight" (while Prometheus means "forethought"). This name cleverly captures the tool's purpose: bringing data to Prometheus **after** collection, whether it's historic data from hours, days, or weeks ago, or realtime data pushed on-demand. + +While Epimetheus is sometimes depicted as foolish in myths (he accepted Pandora's box despite warnings), this tool embraces the "afterthought" aspect productively - it's never too late to bring your metrics home to Prometheus! + +## Architecture + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ Epimetheus │ +│ (Metrics Ingestion Tool) │ +│ │ +│ Modes: │ +│ • Realtime - Current metrics (< 5 min old) │ +│ • Historic - Historic metrics (≥ 5 min old) │ +│ • Backfill - Range of historic data │ +│ • Auto - Automatic routing based on timestamp age │ +└─────────────────────────────────────────────────────────────────────────┘ + │ │ + │ Realtime Data │ Historic Data + │ (via HTTP POST) │ (via Remote Write API) + │ Uses "now" timestamp │ Preserves timestamps + ▼ ▼ +┌─────────────────────┐ ┌─────────────────────┐ +│ Pushgateway │ │ Prometheus │ +│ (Port 9091) │ │ (Port 9090) │ +│ │ │ │ +│ • Buffers metrics │ │ Remote Write API: │ +│ • Scraped by │──── Scraped ─────▶ │ /api/v1/write │ +│ Prometheus │ every 15-30s │ │ +│ • No timestamp │ │ Feature Required: │ +│ preservation │ │ --enable-feature= │ +│ │ │ remote-write- │ +│ │ │ receiver │ +└─────────────────────┘ └─────────────────────┘ + │ + │ Prometheus Query API + │ /api/v1/query + ▼ + ┌─────────────────────┐ + │ Grafana │ + │ (Port 3000) │ + │ │ + │ • Prometheus as │ + │ datasource │ + │ • Dashboards: │ + │ - Epimetheus │ + │ Test Metrics │ + │ • Auto-refresh │ + └─────────────────────┘ +``` + +### Data Flow + +1. **Realtime Path** (for current data): + - Epimetheus → Pushgateway (HTTP POST) + - Prometheus scrapes Pushgateway periodically + - Timestamp = "now" when Prometheus scrapes + +2. **Historic Path** (for old data): + - Epimetheus → Prometheus Remote Write API (HTTP POST) + - Direct write to Prometheus TSDB + - Timestamp preserved from original data + +3. **Visualization**: + - Grafana queries Prometheus + - Displays metrics in dashboards + - Auto-refresh every 10 seconds + +## Overview + +**epimetheus** is a standalone binary that: +- **Generates** realistic example metrics simulating production applications +- **Pushes** metrics via Pushgateway (realtime) or Remote Write API (historic) +- **Automatically detects** timestamp age and chooses the optimal ingestion method +- **Supports** multiple data formats (CSV, JSON) and all Prometheus metric types +- **Provides** Grafana dashboard for visualizing test metrics + +## Quick Start + +### 1. Deploy Pushgateway (one-time setup) + +The Pushgateway Helm chart is available in the [conf repository](https://codeberg.org/snonux/conf) at `f3s/pushgateway/helm-chart`. + +```bash +# Clone the conf repository if you haven't already +git clone https://codeberg.org/snonux/conf.git +cd conf/f3s/pushgateway/helm-chart + +# Deploy Pushgateway +helm upgrade --install pushgateway . -n monitoring --create-namespace +``` + +Alternatively, deploy Pushgateway using the official chart: + +```bash +helm repo add prometheus-community https://prometheus-community.github.io/helm-charts +helm install pushgateway prometheus-community/prometheus-pushgateway -n monitoring --create-namespace +``` + +### 2. Run in Realtime Mode + +```bash +# Port-forward Pushgateway +kubectl port-forward -n monitoring svc/pushgateway 9091:9091 & + +# Push test metrics continuously +cd /home/paul/git/conf/f3s/epimetheus +./epimetheus -mode=realtime -continuous +``` + +The binary pushes metrics every 15 seconds. Press Ctrl+C to stop. + +### 3. View Metrics + +```bash +# Pushgateway UI +open http://localhost:9091 + +# Prometheus UI +kubectl port-forward -n monitoring svc/prometheus-kube-prometheus-prometheus 9090:9090 & +open http://localhost:9090 +``` + +## Operating Modes + +### 👁️ Watch Mode +Monitor CSV files for changes and push metrics to Prometheus with file modification timestamps. + +**Works with ANY CSV format** - automatically detects numeric vs string columns and sanitizes names. + +**NEW: Automatic DNS Resolution** - IP addresses are automatically resolved to hostnames for better observability in Grafana. + +```bash +./epimetheus -mode=watch \ + -file=mydata.csv \ + -metric-name=myapp \ + -prometheus=http://localhost:9090/api/v1/write +``` + +**Features:** +- 🔍 **Format-agnostic**: Works with any tabular CSV structure +- 📊 **Automatic detection**: Numeric columns → metrics, String columns → labels +- 🏷️ **Name sanitization**: `min(potatoes)`, `avg(time)`, `p99(latency)` → valid metric names +- 🌐 **DNS Resolution**: IP addresses → hostnames (e.g., `10.50.52.61` → `foo.example.lan`) +- 💾 **Smart Caching**: In-memory cache prevents redundant DNS lookups +- ⏱️ **Timestamp preservation**: Uses file modification time +- 🔄 **Continuous monitoring**: Polls file every 1 second +- 💪 **Error resilient**: Continues watching despite failures +- 🎯 **Remote Write**: Pushes to Prometheus (preserves timestamps) + +**CSV Format:** +Works with any tabular CSV: +- First row: column headers (automatically sanitized) +- Subsequent rows: data values +- Column names can be anything: `min(x)`, `avg(y)`, `p99(latency)`, etc. + +**Example 1** - Web metrics: +```csv +avg(response_time),p99(latency),endpoint,method +45.2,120.5,/api/users,GET +52.1,135.8,/api/orders,POST +``` + +Generates: +```promql +web_avg_response_time{endpoint="/api/users",method="GET"} 45.2 +web_p99_latency{endpoint="/api/users",method="GET"} 120.5 +web_avg_response_time{endpoint="/api/orders",method="POST"} 52.1 +web_p99_latency{endpoint="/api/orders",method="POST"} 135.8 +``` + +**Example 2** - Food metrics: +```csv +min(potatoes),last(coke),avg(price),country,store_type +5.2,10.5,12.99,USA,grocery +3.8,8.2,9.99,Canada,convenience +``` + +Generates: +```promql +food_min_potatoes{country="USA",store_type="grocery"} 5.2 +food_last_coke{country="USA",store_type="grocery"} 10.5 +food_avg_price{country="USA",store_type="grocery"} 12.99 +# ... etc +``` + +Each row generates N samples (N = number of numeric columns). + +See [CSV-FORMAT-FLEXIBILITY.md](CSV-FORMAT-FLEXIBILITY.md) for more examples. + +**Options:** +- `-file` - CSV file to watch (required) +- `-metric-name` - Base metric name (required, e.g., `food`, `network`, `database`) +- `-prometheus` - Prometheus Remote Write URL (default: http://localhost:9090/api/v1/write) +- `-clickhouse` - ClickHouse HTTP URL (e.g. http://localhost:8123) to also ingest metrics +- `-clickhouse-table` - ClickHouse table name (default: epimetheus_metrics) +- `-job` - Job name for metrics (default: example_metrics_pusher) +- `-resolve-ip-labels` - Additional IP labels to resolve via DNS (default: ip is always resolved) + +**ClickHouse Support:** +Watch mode can ingest to ClickHouse in addition to (or instead of) Prometheus: + +```bash +# Ingest to both Prometheus and ClickHouse +./epimetheus -mode=watch -file=data.csv -metric-name=myapp \ + -prometheus=http://localhost:9090/api/v1/write \ + -clickhouse=http://localhost:8123 + +# ClickHouse only (use -prometheus= to disable Prometheus) +./epimetheus -mode=watch -file=test-data/watch-clickhouse-test.csv \ + -metric-name=watch_test -clickhouse=http://localhost:8123 -prometheus= + +# Verify data in ClickHouse +./verify-clickhouse.sh +``` + +**DNS Resolution:** +By default, the `ip` label is automatically resolved to a hostname. To resolve additional IP labels: + +```bash +./epimetheus -mode=watch \ + -file=network.csv \ + -metric-name=network \ + -resolve-ip-labels=source_ip,dest_ip +``` + +This will resolve: `ip` (default) + `source_ip` + `dest_ip` + +**Example:** +- Input: `ip="10.50.52.61"` +- Output: `ip="foo.example.lan"` +- Failed lookups: IP remains unchanged + +**Documentation:** +- [DNS-RESOLUTION-FEATURE.md](DNS-RESOLUTION-FEATURE.md) - Complete DNS resolution guide +- [CSV-FORMAT-FLEXIBILITY.md](CSV-FORMAT-FLEXIBILITY.md) - Works with ANY CSV format +- [DTAIL-METRICS-EXAMPLE.md](DTAIL-METRICS-EXAMPLE.md) - Detailed dtail.csv example + +### 🔄 Realtime Mode (Default) +Push current metrics to Pushgateway with "now" timestamp. + +```bash +./epimetheus -mode=realtime -continuous +``` + +**Options:** +- `-pushgateway` - Pushgateway URL (default: http://localhost:9091) +- `-job` - Job name (default: example_metrics_pusher) +- `-continuous` - Keep pushing every 15 seconds + +### ⏰ Historic Mode +Push a single datapoint from the past using Remote Write API. + +```bash +# Port-forward Prometheus +kubectl port-forward -n monitoring svc/prometheus-kube-prometheus-prometheus 9090:9090 & + +# Push data from 24 hours ago +./epimetheus -mode=historic -hours-ago=24 +``` + +**Options:** +- `-prometheus` - Prometheus URL (default: http://localhost:9090/api/v1/write) +- `-hours-ago` - Hours in the past (default: 24) + +### 📦 Backfill Mode +Import a range of historic data points. + +```bash +# Backfill last 48 hours with 1-hour intervals +./epimetheus -mode=backfill -start-hours=48 -end-hours=0 -interval=1 + +# Backfill last week with 6-hour intervals +./epimetheus -mode=backfill -start-hours=168 -end-hours=0 -interval=6 +``` + +**Options:** +- `-start-hours` - Start time in hours ago +- `-end-hours` - End time in hours ago (0 = now) +- `-interval` - Interval between points in hours + +### 🤖 Auto Mode (Recommended!) +Automatically detect timestamp age and route to the correct ingestion method. + +```bash +# Generate test data +./generate-test-data.sh + +# Import mixed current and historic data +./epimetheus -mode=auto -file=test-all-ages.csv +``` + +**Detection Logic:** +- Data < 5 minutes old → Pushgateway (realtime) +- Data ≥ 5 minutes old → Remote Write (historic) + +**Options:** +- `-file` - Input file path +- `-format` - Data format: csv or json (default: csv) +- `-pushgateway` - Pushgateway URL +- `-prometheus` - Prometheus Remote Write URL + +## Data Formats + +### CSV Format + +```csv +# Format: metric_name,labels,value,timestamp_ms +# Labels: key1=value1;key2=value2 +epimetheus_test_requests_total,instance=web1;env=prod,100,1767125148000 +epimetheus_test_temperature_celsius,instance=web2,22.5,1767038748000 + +# Timestamp is optional (uses "now" if omitted) +epimetheus_test_active_connections,instance=web3,42, +``` + +### JSON Format + +```json +[ + { + "metric": "epimetheus_test_requests_total", + "labels": {"instance": "web1", "env": "prod"}, + "value": 100, + "timestamp_ms": 1767125148000 + }, + { + "metric": "epimetheus_test_temperature_celsius", + "labels": {"instance": "web2"}, + "value": 22.5, + "timestamp_ms": 1767038748000 + } +] +``` + +## Test Metrics + +All generated metrics use the `epimetheus_test_` prefix to clearly identify them as test data. + +### Counter: `epimetheus_test_requests_total` +- **Type:** Counter (monotonically increasing) +- **Description:** Total number of requests processed +- **Use case:** Counting total events, requests, errors + +### Gauge: `epimetheus_test_active_connections` +- **Type:** Gauge (can increase or decrease) +- **Description:** Current number of active connections (0-100) +- **Use case:** Current state measurements, capacity + +### Gauge: `epimetheus_test_temperature_celsius` +- **Type:** Gauge +- **Description:** Current temperature in Celsius (0-50°C) +- **Use case:** Environmental monitoring + +### Histogram: `epimetheus_test_request_duration_seconds` +- **Type:** Histogram (distribution) +- **Description:** Request duration distribution +- **Buckets:** 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10 seconds +- **Use case:** Latency measurements, SLO tracking + +### Labeled Counter: `epimetheus_test_jobs_processed_total` +- **Type:** Counter with labels +- **Description:** Jobs processed by type and status +- **Labels:** + - `job_type`: email, report, backup + - `status`: success, failed +- **Use case:** Categorized counting, multi-dimensional metrics + +## Grafana Dashboard + +A comprehensive dashboard is available showcasing all test metrics. + +### Dashboard Features + +- **8 Panels:** + 1. Request Rate (line graph) + 2. Total Requests (stat panel) + 3. Active Connections (gauge with thresholds) + 4. Temperature (gauge with thresholds) + 5. Request Duration Histogram (p50, p90, p99) + 6. Average Request Duration (stat) + 7. Jobs Processed by Type (bar gauge) + 8. Jobs Status Breakdown (table) + +- **Auto-refresh:** Every 10 seconds +- **Time range:** Last 15 minutes (customizable) +- **Dark theme optimized** + +### Deploy Dashboard + +#### Option 1: Helm/Kubernetes ConfigMap (Recommended) + +```bash +# Deploy via Kubernetes ConfigMap +kubectl apply -f ../prometheus/epimetheus-dashboard.yaml +``` + +The dashboard will be automatically discovered by Grafana. + +#### Option 2: Manual Import + +```bash +# Port-forward Grafana +kubectl port-forward -n monitoring svc/prometheus-grafana 3000:80 + +# Open Grafana +open http://localhost:3000 + +# Go to Dashboards → Import → Upload grafana-dashboard.json +``` + +#### Option 3: Automated Script + +```bash +# Deploy via API +./deploy-dashboard.sh + +# Or with custom credentials +GRAFANA_URL="http://localhost:3000" \ +GRAFANA_USER="admin" \ +GRAFANA_PASSWORD="yourpassword" \ +./deploy-dashboard.sh +``` + +## Example Queries + +### Basic Queries + +```promql +# View total requests +epimetheus_test_requests_total + +# View request rate over last 5 minutes +rate(epimetheus_test_requests_total[5m]) + +# View current active connections +epimetheus_test_active_connections + +# View current temperature +epimetheus_test_temperature_celsius +``` + +### Histogram Queries + |
