From e0a852830be2a5b401aaa237a45d3170b03a997a Mon Sep 17 00:00:00 2001 From: Tushar Khatri Date: Thu, 19 Feb 2026 02:58:12 +0530 Subject: [PATCH] chore: remove unused Maven wrapper files (mvnw, mvnw.cmd, .mvn/) --- .mvn/wrapper/maven-wrapper.properties | 3 - DEPLOYMENT.md => DEPLOYMENT_GUIDE.md | 0 EVENTARA_TECHNICAL_REPORT.md | 907 ------------------ PHASE_4_COMPLETION_SUMMARY.md | 612 ------------ RELEASE_v0.1.1.md | 156 --- THRESHOLD_RULES_COMPLETE.md | 567 ----------- .../RULE_EXAMPLES.md | 0 .../agents}/.obsidian/app.json | 0 .../agents}/.obsidian/appearance.json | 0 .../agents}/.obsidian/core-plugins.json | 0 .../agents}/.obsidian/workspace.json | 0 .../agents}/01-mode-a-deprecation-plan.md | 0 .../agents}/02-adaptive-enhancement-plan.md | 0 .../03-ui-backend-compatibility-analysis.md | 0 .../agents}/04-yc-demo-script.md | 0 .../ADAPTIVE_RATE_BASED_EVALUATION.md | 0 .../ADAPTIVE_RATE_BASED_EVALUATION_PLAN.md | 0 .../PHASE_1_CONFIGURATION.md | 0 .../PHASE_2_EVENT_RATE_MONITOR.md | 0 .../PHASE_3_EVALUATION_KEY.md | 0 .../PHASE_4_ADAPTIVE_RULE_EVALUATOR.md | 0 .../PHASE_5_INTEGRATION.md | 0 .../adaptive-evaluation/PHASE_6_SCHEDULING.md | 0 .../adaptive-evaluation/PHASE_7_TESTING.md | 0 .../agents}/adaptive-evaluation/README.md | 0 .../agents}/page-2026-02-09-10-30-47.md | 0 mvnw | 295 ------ mvnw.cmd | 189 ---- tests/requirements.txt | 3 - tests/test_threshold_rules.py | 850 ---------------- tests/yc-demo-events.sh | 2 +- 31 files changed, 1 insertion(+), 3583 deletions(-) delete mode 100644 .mvn/wrapper/maven-wrapper.properties rename DEPLOYMENT.md => DEPLOYMENT_GUIDE.md (100%) delete mode 100644 EVENTARA_TECHNICAL_REPORT.md delete mode 100644 PHASE_4_COMPLETION_SUMMARY.md delete mode 100644 RELEASE_v0.1.1.md delete mode 100644 THRESHOLD_RULES_COMPLETE.md rename RULE_EXAMPLES.md => documents/RULE_EXAMPLES.md (100%) rename {ai-docs => documents/agents}/.obsidian/app.json (100%) rename {ai-docs => documents/agents}/.obsidian/appearance.json (100%) rename {ai-docs => documents/agents}/.obsidian/core-plugins.json (100%) rename {ai-docs => documents/agents}/.obsidian/workspace.json (100%) rename {ai-docs => documents/agents}/01-mode-a-deprecation-plan.md (100%) rename {ai-docs => documents/agents}/02-adaptive-enhancement-plan.md (100%) rename {ai-docs => documents/agents}/03-ui-backend-compatibility-analysis.md (100%) rename {ai-docs => documents/agents}/04-yc-demo-script.md (100%) rename {ai-docs => documents/agents}/adaptive-evaluation/ADAPTIVE_RATE_BASED_EVALUATION.md (100%) rename {ai-docs => documents/agents}/adaptive-evaluation/ADAPTIVE_RATE_BASED_EVALUATION_PLAN.md (100%) rename {ai-docs => documents/agents}/adaptive-evaluation/PHASE_1_CONFIGURATION.md (100%) rename {ai-docs => documents/agents}/adaptive-evaluation/PHASE_2_EVENT_RATE_MONITOR.md (100%) rename {ai-docs => documents/agents}/adaptive-evaluation/PHASE_3_EVALUATION_KEY.md (100%) rename {ai-docs => documents/agents}/adaptive-evaluation/PHASE_4_ADAPTIVE_RULE_EVALUATOR.md (100%) rename {ai-docs => documents/agents}/adaptive-evaluation/PHASE_5_INTEGRATION.md (100%) rename {ai-docs => documents/agents}/adaptive-evaluation/PHASE_6_SCHEDULING.md (100%) rename {ai-docs => documents/agents}/adaptive-evaluation/PHASE_7_TESTING.md (100%) rename {ai-docs => documents/agents}/adaptive-evaluation/README.md (100%) rename {ai-docs => documents/agents}/page-2026-02-09-10-30-47.md (100%) delete mode 100755 mvnw delete mode 100644 mvnw.cmd delete mode 100644 tests/requirements.txt delete mode 100644 tests/test_threshold_rules.py diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties deleted file mode 100644 index c0bcafe..0000000 --- a/.mvn/wrapper/maven-wrapper.properties +++ /dev/null @@ -1,3 +0,0 @@ -wrapperVersion=3.3.4 -distributionType=only-script -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip diff --git a/DEPLOYMENT.md b/DEPLOYMENT_GUIDE.md similarity index 100% rename from DEPLOYMENT.md rename to DEPLOYMENT_GUIDE.md diff --git a/EVENTARA_TECHNICAL_REPORT.md b/EVENTARA_TECHNICAL_REPORT.md deleted file mode 100644 index 42f38c2..0000000 --- a/EVENTARA_TECHNICAL_REPORT.md +++ /dev/null @@ -1,907 +0,0 @@ -# Eventara - Technical Codebase Report -## Comprehensive Technical Documentation for Y Combinator Application - ---- - -## Executive Summary - -**Eventara** is an open-source, self-hosted real-time event monitoring and alerting platform designed to provide enterprise-grade observability at zero licensing cost. The platform enables organizations to ingest events from any application, define intelligent alerting rules, and receive instant notifications when anomalies occur. - -### Key Value Propositions -- **Zero Cost**: Completely open-source, eliminating $200-2000+/month monitoring costs -- **Self-Hosted**: Full data sovereignty and privacy compliance -- **Real-Time**: Sub-second event processing with live WebSocket dashboard -- **Intelligent Alerting**: Rule-based alerting using the Drools rule engine with threshold-based rules (Pattern, Anomaly, and CEP rules planned) -- **Multi-Channel Notifications**: Webhook support (Email, Slack, SMS, PagerDuty planned) - -### Current Status -- **Stage**: v0.1.0 - First Public Release -- **Readiness**: Production-ready for threshold-based monitoring use cases -- **Architecture**: Fully containerized, Docker Compose deployment ready - ---- - -## System Architecture - -``` -┌─────────────────────────────────────────────────────────────────────────┐ -│ Your Applications │ -│ (Microservices, APIs, IoT Devices, Mobile Apps) │ -└────────────────────────────┬────────────────────────────────────────────┘ - │ HTTP REST Events - ▼ -┌─────────────────────────────────────────────────────────────────────────┐ -│ EVENTARA PLATFORM │ -│ │ -│ ┌──────────────────┐ ┌──────────────────┐ ┌─────────────────┐ │ -│ │ Event Ingestion │────▶│ Apache Kafka │───▶│ Event Consumer │ │ -│ │ Controller │ │ (Message Queue) │ │ + Metrics │ │ -│ │ /api/v1/events │ │ events-raw topic│ │ Recording │ │ -│ └──────────────────┘ └──────────────────┘ └────────┬────────┘ │ -│ │ │ -│ ┌──────────────────────────┤ │ -│ │ │ │ -│ ▼ ▼ │ -│ ┌──────────────────┐ ┌──────────────────┐ ┌─────────────────┐ │ -│ │ PostgreSQL │◀────│ Comprehensive │ │ Drools Rule │ │ -│ │ (Event Storage) │ │ Metrics Service │ │ Engine │ │ -│ │ │ │ (Real-time Stats)│ │ (Alert Trigger) │ │ -│ └──────────────────┘ └────────┬─────────┘ └────────┬────────┘ │ -│ │ │ │ -│ ▼ ▼ │ -│ ┌──────────────────┐ ┌─────────────────┐ │ -│ │ WebSocket │ │ Notification │ │ -│ │ Push (1s) │ │ Service │ │ -│ │ /topic/metrics │ │ (Webhook, etc.) │ │ -│ └────────┬─────────┘ └─────────────────┘ │ -│ │ │ -└───────────────────────────────────┼───────────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────────────────────────────────┐ -│ React Dashboard (Vite + TypeScript) │ -│ │ -│ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ -│ │ Overview │ │ Real-Time │ │ Event │ │ Rules │ │ Settings │ │ -│ │ │ │ Monitoring│ │ Analytics │ │ Editor │ │ Notif. │ │ -│ └───────────┘ └───────────┘ └───────────┘ └───────────┘ └───────────┘ │ -└─────────────────────────────────────────────────────────────────────────┘ -``` - ---- - -## Technology Stack - -| Layer | Technology | Version | Purpose | -|-------|------------|---------|---------| -| **Backend** | Spring Boot | 3.5.7 | REST API, Dependency Injection, WebSocket | -| **Language** | Java | 21 | LTS, Virtual Threads, Modern Features | -| **Message Queue** | Apache Kafka | 7.5.0 | Event streaming, decoupling | -| **Database** | PostgreSQL | 14 | Event storage, Rule persistence | -| **Rule Engine** | Drools | 8.44.0 | Dynamic DRL rule execution | -| **Frontend** | React | 19.2.0 | Modern UI framework | -| **Build Tool** | Vite | 7.2.4 | Fast HMR, ES Modules | -| **Styling** | Tailwind CSS | 3.4.18 | Utility-first CSS | -| **Charts** | Chart.js | 4.5.1 | Data visualization | -| **Real-Time** | STOMP/SockJS | Latest | WebSocket communication | -| **DB Migration** | Flyway | Latest | Schema versioning | -| **Templating** | Thymeleaf | Latest | Email HTML templates | -| **Scheduling** | Quartz | Latest | Rule evaluation scheduling | -| **Container** | Docker + Compose | Latest | Deployment orchestration | - ---- - -## Backend Architecture (Spring Boot) - -### Module Structure - -``` -src/main/java/com/eventara/ -├── EventaraApplication.java # Main entry point -├── ingestion/ # Event ingestion module -│ ├── controller/ # REST endpoints -│ │ └── EventController.java -│ ├── service/ -│ │ └── EventService.java -│ ├── kafka/ -│ │ ├── EventProducer.java # Kafka publisher -│ │ └── EventConsumer.java # Kafka subscriber -│ ├── mapper/ -│ │ └── EventMapper.java -│ └── model/entity/ -│ └── Event.java # JPA entity -│ -├── analytics/ # Real-time analytics module -│ ├── controller/ -│ │ ├── MetricsController.java -│ │ └── MetricsWebSocketController.java -│ └── service/ -│ ├── ComprehensiveMetricsService.java # Core metrics -│ └── MetricsAggregationService.java -│ -├── rule/ # Alert rule management -│ ├── controller/ -│ │ ├── RuleController.java -│ │ └── RuleTestController.java -│ ├── service/ -│ │ ├── RuleService.java # Interface -│ │ ├── RuleServiceImpl.java # Implementation -│ │ ├── DrlGeneratorService.java # Drools DRL generation -│ │ ├── RuleCompilerService.java -│ │ └── RuleValidationService.java -│ ├── entity/ -│ │ └── AlertRule.java -│ ├── dto/ -│ │ ├── request/ -│ │ │ ├── CreateRuleRequest.java -│ │ │ ├── UpdateRuleRequest.java -│ │ │ └── TestRuleRequest.java -│ │ └── response/ -│ │ ├── RuleResponse.java -│ │ └── RuleTestResult.java -│ ├── enums/ -│ │ ├── MetricType.java # 28 metric types -│ │ ├── Condition.java # 8 comparison operators -│ │ ├── RuleType.java # THRESHOLD, PATTERN, ANOMALY, CEP -│ │ └── RuleStatus.java # ACTIVE, INACTIVE, ARCHIVED, DRAFT -│ └── repository/ -│ └── RuleRepository.java -│ -├── alert/ # Alert handling -│ ├── entity/ -│ │ └── AlertHistory.java -│ ├── service/ -│ │ └── AlertTriggerHandler.java # Called by Drools -│ ├── enums/ -│ │ ├── AlertSeverity.java # CRITICAL, WARNING, INFO -│ │ └── AlertStatus.java -│ └── repository/ -│ └── AlertHistoryRepository.java -│ -├── notification/ # Multi-channel notifications -│ ├── controller/ -│ │ ├── NotificationChannelController.java -│ │ └── NotificationLogController.java -│ ├── service/ -│ │ ├── NotificationService.java # Interface -│ │ ├── NotificationServiceImpl.java # Implementation -│ │ └── WebhookNotificationHandler.java # Webhook delivery -│ ├── entity/ -│ │ ├── NotificationChannel.java -│ │ └── NotificationLog.java -│ ├── enums/ -│ │ ├── ChannelType.java # WEBHOOK, EMAIL, SLACK, SMS, PAGERDUTY -│ │ └── NotificationStatus.java -│ └── dto/ -│ └── NotificationMessage.java -│ -├── drools/ # Rule execution engine -│ ├── service/ -│ │ └── RuleExecutionService.java # KIE session management -│ └── fact/ -│ └── MetricsFact.java # Facts for rule evaluation -│ -└── common/ # Shared utilities - ├── dto/ - │ ├── EventDto.java - │ ├── EventRequest.java - │ ├── EventResponse.java - │ └── ComprehensiveMetricsDto.java - └── repository/ - └── EventRepository.java -``` - ---- - -## Data Flow: Event Ingestion to Alert - -### Step-by-Step Flow - -``` -1. CLIENT → POST /api/v1/events - { - "eventType": "payment.failed", - "source": "payment-service", - "userId": "user_123", - "severity": "ERROR" - } - -2. EventController.ingestEvent() - └── Validates request - └── Returns HTTP 202 Accepted immediately - -3. EventService.processEvent() - └── Maps request to Event entity - └── Generates unique eventId (UUID) - └── Passes to EventProducer - -4. EventProducer.sendEvent() - └── Publishes to Kafka topic "events-raw" - └── Uses eventId as partition key - └── Async callback logs success/failure - -5. [Kafka stores event in topic partition] - -6. EventConsumer.ConsumeEvent() - └── @KafkaListener receives event - └── Checks for duplicate (deduplication) - └── Saves to PostgreSQL via EventRepository - └── Converts to EventDto - └── Calls ComprehensiveMetricsService.recordEvent() - └── Manually acknowledges Kafka offset - -7. ComprehensiveMetricsService.recordEvent() - └── Increments total event counter - └── Updates eventsByType, eventsBySource maps - └── Tracks latency metrics - └── Maintains 24-hour sliding window - └── Detects anomalies (>5% error rate, P95 >1s, 3x traffic spike) - -8. MetricsWebSocketController (@Scheduled every 1s) - └── Calls getComprehensiveMetrics() - └── Pushes to WebSocket /topic/metrics - └── Dashboard receives real-time update - -9. RuleExecutionService (Scheduled by timeWindowMinutes) - └── Groups active rules by evaluation interval - └── Creates KieSession for each group - └── Injects current MetricsFact - └── Fires all matching rules - -10. [If Threshold Rule Matches] - └── DRL rule condition evaluates true - └── Calls AlertTriggerHandler.handleThresholdAlert() - -11. AlertTriggerHandler.handleThresholdAlert() - └── Checks suppression window (default 30 min) - └── Creates AlertHistory entity - └── Saves to database - └── Updates rule trigger count - └── Calls NotificationService.sendNotification() - -12. NotificationServiceImpl.sendNotification() - └── Fetches configured channels from rule - └── Checks rate limits - └── Routes to appropriate handler (WebhookNotificationHandler) - └── Logs notification attempt - └── Updates AlertHistory with notification results -``` - ---- - -## Rule Engine: Threshold Rules Deep Dive - -### Currently Implemented: THRESHOLD Rules - -Threshold rules compare a metric value against a threshold using a condition. When the condition is met, an alert fires. - -#### Available Metric Types (28 metrics) - -```java -// Error Metrics -ERROR_RATE, // Percentage of events that are errors -TOTAL_ERRORS, // Absolute error count - -// Performance Metrics -AVG_LATENCY, // Average processing latency (ms) -P50_LATENCY, // 50th percentile latency -P95_LATENCY, // 95th percentile latency -P99_LATENCY, // 99th percentile latency -MAX_LATENCY, // Maximum observed latency -MIN_LATENCY, // Minimum observed latency - -// Throughput Metrics -EVENTS_PER_SECOND, // Current events/second -EVENTS_PER_MINUTE, // Current events/minute -EVENTS_PER_HOUR, // Hourly projection -EVENTS_PER_DAY, // Daily projection -PEAK_THROUGHPUT, // Highest observed throughput -AVG_THROUGHPUT_1H, // Average over last hour -AVG_THROUGHPUT_24H, // Average over last 24 hours - -// Time Window Metrics -EVENTS_LAST_1_MINUTE, -EVENTS_LAST_5_MINUTES, -EVENTS_LAST_15_MINUTES, -EVENTS_LAST_1_HOUR, -EVENTS_LAST_24_HOURS, - -// Summary Metrics -TOTAL_EVENTS, // All-time event count -UNIQUE_SOURCES, // Number of distinct sources -UNIQUE_EVENT_TYPES, // Number of distinct event types -UNIQUE_USERS, // Number of distinct users -SYSTEM_HEALTH, // "healthy", "degraded", "critical" - -// User Metrics -ACTIVE_USERS_LAST_1_HOUR, -ACTIVE_USERS_LAST_24_HOURS, -TOTAL_UNIQUE_USERS -``` - -#### Available Conditions - -```java -GREATER_THAN(">"), -LESS_THAN("<"), -EQUALS("=="), -GREATER_THAN_OR_EQUAL(">="), -LESS_THAN_OR_EQUAL("<="), -NOT_EQUALS("!="), -BETWEEN("between"), // Not yet implemented -NOT_BETWEEN("not between") // Not yet implemented -``` - -### Rule Creation Flow - -``` -1. User creates rule via dashboard: - POST /api/v1/rules - { - "name": "High Error Rate Alert", - "ruleType": "THRESHOLD", - "severity": "CRITICAL", - "priority": 10, - "ruleConfig": { - "metricType": "ERROR_RATE", - "condition": "GREATER_THAN", - "thresholdValue": 5.0, - "timeWindowMinutes": 5 - }, - "notificationChannels": ["slack-alerts"], - "suppressionWindowMinutes": 30, - "maxAlertsPerHour": 10 - } - -2. RuleServiceImpl.createRule(): - - Validates request - - Calls DrlGeneratorService.generateDrl() - - Generates DRL hash for change detection - - Saves AlertRule to PostgreSQL - - Triggers RuleExecutionService.reloadRule() - -3. DrlGeneratorService.generateThresholdDrl(): - - Generates Drools Rule Language code: - - package com.eventara.rules - - import com.eventara.drools.fact.MetricsFact - import com.eventara.alert.service.AlertTriggerHandler - - global com.eventara.alert.service.AlertTriggerHandler alertHandler; - - rule "High Error Rate Alert" - salience 10 - when - $metrics: MetricsFact( - (currentTimeSeconds / 60) % 5 == 0, - errorRate > 5.0 - ) - $handler: AlertTriggerHandler() - then - $handler.handleThresholdAlert( - 1L, - "High Error Rate Alert", - "CRITICAL", - 5.0, - $metrics.getErrorRate() - ); - end - -4. RuleExecutionService: - - Groups rules by timeWindowMinutes - - Creates separate KieContainer per group - - Schedules evaluation at fixed intervals - - Aligns to clock (5-min rules fire at :00, :05, :10...) -``` - -### Rule Lifecycle - -| Status | Description | -|--------|-------------| -| `ACTIVE` | Rule is loaded in Drools, being evaluated | -| `INACTIVE` | Rule exists but not evaluated | -| `ARCHIVED` | Rule is soft-deleted, hidden from UI | -| `DRAFT` | Rule under construction, not yet enabled | - -### Alert Suppression & Rate Limiting - -```java -// On AlertRule entity: -suppressionWindowMinutes = 30 // Default: suppress duplicates for 30 min -maxAlertsPerHour = 10 // Default: max 10 alerts per hour per rule -``` - -This prevents alert fatigue by: -1. Checking if same rule triggered within suppression window -2. Enforcing max alerts per hour limit - ---- - -## Notification System - -### Implemented: Webhook Notifications - -```java -// NotificationChannel entity -{ - "channelType": "WEBHOOK", - "name": "slack-alerts", - "config": { - "url": "https://hooks.slack.com/services/...", - "method": "POST", - "headers": { "Content-Type": "application/json" } - }, - "rateLimitPerMinute": 60, - "rateLimitPerHour": 1000 -} -``` - -### Notification Flow - -``` -AlertTriggerHandler - └── sendNotifications(alert, ruleId) - └── Builds NotificationMessage - └── Fetches channel names from rule - └── Calls NotificationService.sendNotification() async - -NotificationServiceImpl - └── For each channel: - └── Validate channel enabled - └── Check rate limits - └── Route to WebhookNotificationHandler - └── Log attempt - └── Update channel stats - -WebhookNotificationHandler - └── Build HTTP request from config - └── Send POST with alert payload - └── Record response code & body - └── Return NotificationResult -``` - -### Notification Payload Example - -```json -{ - "alertId": 123, - "ruleName": "High Error Rate Alert", - "severity": "CRITICAL", - "subject": "Alert: High Error Rate Alert", - "message": "Alert: High Error Rate Alert - Threshold: 5.00, Actual: 7.23", - "thresholdValue": 5.0, - "actualValue": 7.23, - "triggeredAt": "2026-01-28T15:30:00", - "context": { - "ruleName": "High Error Rate Alert", - "thresholdValue": 5.0, - "actualValue": 7.23, - "timestamp": "2026-01-28T15:30:00" - } -} -``` - -### Planned Notification Channels - -| Channel | Implementation Status | -|---------|----------------------| -| Webhook | ✅ Implemented | -| Email | 🚧 Template ready (Thymeleaf), handler TODO | -| Slack | 📋 Planned | -| SMS | 📋 Planned | -| PagerDuty | 📋 Planned | - ---- - -## Real-Time Dashboard (React) - -### Frontend Architecture - -``` -eventara-dashboard/src/ -├── App.tsx # Router setup -├── main.tsx # Entry point -├── index.css # Design system (TailwindCSS) -│ -├── hooks/ -│ └── useWebSocket.ts # STOMP/SockJS connection -│ -├── pages/ -│ ├── Overview.tsx # Hero metrics, charts, alerts -│ ├── RealTimeMonitoring.tsx # Live gauges, throughput -│ ├── EventAnalytics.tsx # Event type breakdown -│ ├── RulesList.tsx # Rule management table -│ ├── RuleEditor.tsx # Create/edit rules form -│ ├── RuleDetails.tsx # Single rule view -│ ├── SettingsNotifications.tsx # Notification channels -│ ├── AlertsAndAnomalies.tsx # Alert history -│ ├── PerformanceMetrics.tsx # Latency analysis -│ ├── ErrorAnalysis.tsx # Error tracking -│ ├── SourceAnalytics.tsx # Coming soon -│ └── UserAnalytics.tsx # Coming soon -│ -├── components/ -│ ├── layout/ -│ │ ├── DashboardLayout.tsx # Sidebar + Header wrapper -│ │ ├── Sidebar.tsx # Navigation -│ │ ├── Header.tsx # Top bar -│ │ └── ConnectionStatus.tsx # WebSocket indicator -│ ├── cards/ -│ │ ├── HeroMetricCard.tsx -│ │ ├── SystemHealthBadge.tsx -│ │ ├── QuickStatsGrid.tsx -│ │ └── ... -│ ├── charts/ -│ │ ├── ThroughputChart.tsx -│ │ ├── EventsOverTimeChart.tsx -│ │ ├── CircularGauge.tsx -│ │ └── ... -│ ├── alerts/ -│ │ ├── RecentAlertsPanel.tsx -│ │ └── ActiveAnomaliesAlert.tsx -│ └── rules/ -│ └── RuleTestModal.tsx -│ -├── types/ -│ ├── metrics.ts # ComprehensiveMetrics type -│ ├── rules.ts # Rule DTOs -│ ├── notifications.ts # Channel DTOs -│ └── websocket.ts # Connection state -│ -└── utils/ - └── api/ - ├── rules.ts # Rule API calls - └── notifications.ts # Notification API calls -``` - -### WebSocket Connection - -```typescript -// useWebSocket.ts -const stompClient = new Client({ - webSocketFactory: () => new SockJS('http://localhost:8080/ws'), - heartbeatIncoming: 4000, - heartbeatOutgoing: 4000, - reconnectDelay: 3000 -}); - -stompClient.onConnect = () => { - // Subscribe to metrics topic - stompClient.subscribe('/topic/metrics', (message) => { - const metrics: ComprehensiveMetrics = JSON.parse(message.body); - setMetrics(metrics); - }); - - // Request initial metrics - stompClient.publish({ - destination: '/app/subscribe', - body: JSON.stringify({ action: 'subscribe' }) - }); -}; -``` - -### Metrics Update Frequency - -- **WebSocket push**: Every 1 second -- **Metrics calculation**: Real-time (on each event) -- **Dashboard rendering**: React state update on new message - ---- - -## API Reference - -### Event Ingestion APIs - -| Method | Endpoint | Description | -|--------|----------|-------------| -| POST | `/api/v1/events` | Ingest single event | -| POST | `/api/v1/events/batch` | Batch ingestion (planned) | -| GET | `/api/v1/events` | List events (paginated) | -| GET | `/api/v1/events/{eventId}` | Get event by ID | -| GET | `/api/v1/events/type/{type}` | Get events by type | -| GET | `/api/v1/events/test` | API health check | - -### Rule Management APIs - -| Method | Endpoint | Description | -|--------|----------|-------------| -| POST | `/api/v1/rules` | Create new rule | -| GET | `/api/v1/rules` | List all rules | -| GET | `/api/v1/rules/{id}` | Get rule by ID | -| PUT | `/api/v1/rules/{id}` | Update rule | -| DELETE | `/api/v1/rules/{id}` | Delete rule | -| POST | `/api/v1/rules/{id}/enable` | Enable rule | -| POST | `/api/v1/rules/{id}/disable` | Disable rule | -| POST | `/api/v1/rules/{id}/archive` | Archive rule | -| GET | `/api/v1/rules/active` | List active rules | -| GET | `/api/v1/rules/type/{type}` | Filter by rule type | -| GET | `/api/v1/rules/status/{status}` | Filter by status | -| GET | `/api/v1/rules/search?q=` | Search rules | -| GET | `/api/v1/rules/statistics` | Get counts | -| POST | `/api/v1/rules/test` | Test rule configuration | -| POST | `/api/v1/rules/test/{id}` | Test existing rule | - -### Notification APIs - -| Method | Endpoint | Description | -|--------|----------|-------------| -| POST | `/api/v1/notifications/channels` | Create channel | -| GET | `/api/v1/notifications/channels` | List channels | -| PUT | `/api/v1/notifications/channels/{id}` | Update channel | -| DELETE | `/api/v1/notifications/channels/{id}` | Delete channel | -| POST | `/api/v1/notifications/channels/{id}/test` | Test channel | -| GET | `/api/v1/notifications/logs` | List notification logs | - -### Metrics APIs - -| Method | Endpoint | Description | -|--------|----------|-------------| -| GET | `/api/v1/metrics` | Get current metrics | -| WebSocket | `/topic/metrics` | Real-time metrics stream | -| WebSocket | `/app/subscribe` | Subscribe to metrics | - ---- - -## Database Schema - -### Core Tables - -```sql --- Events table -CREATE TABLE events ( - id BIGSERIAL PRIMARY KEY, - event_id VARCHAR(255) UNIQUE NOT NULL, - event_type VARCHAR(255) NOT NULL, - source VARCHAR(255) NOT NULL, - user_id VARCHAR(255), - severity VARCHAR(50), - timestamp TIMESTAMP NOT NULL, - processing_latency_ms BIGINT, - tags JSONB, - metadata JSONB, - is_error BOOLEAN DEFAULT FALSE -); - --- Alert rules table -CREATE TABLE alert_rules ( - id BIGSERIAL PRIMARY KEY, - name VARCHAR(255) UNIQUE NOT NULL, - description TEXT, - rule_type VARCHAR(50) NOT NULL, - status VARCHAR(50) NOT NULL DEFAULT 'ACTIVE', - rule_config JSONB NOT NULL, - generated_drl TEXT NOT NULL, - drl_hash VARCHAR(255), - severity VARCHAR(50) NOT NULL, - priority INTEGER DEFAULT 0, - notification_channels TEXT[], - notification_config JSONB, - suppression_window_minutes INTEGER DEFAULT 30, - max_alerts_per_hour INTEGER DEFAULT 10, - created_by VARCHAR(255), - created_at TIMESTAMP NOT NULL, - updated_at TIMESTAMP NOT NULL, - last_triggered_at TIMESTAMP, - trigger_count INTEGER DEFAULT 0, - version INTEGER DEFAULT 1 -); - --- Alert history table -CREATE TABLE alert_history ( - id BIGSERIAL PRIMARY KEY, - rule_id BIGINT REFERENCES alert_rules(id), - rule_name VARCHAR(255) NOT NULL, - rule_version INTEGER, - severity VARCHAR(50) NOT NULL, - priority INTEGER, - message TEXT NOT NULL, - description TEXT, - threshold_value DOUBLE PRECISION, - actual_value DOUBLE PRECISION, - status VARCHAR(50) NOT NULL, - triggered_at TIMESTAMP NOT NULL, - acknowledged_at TIMESTAMP, - acknowledged_by VARCHAR(255), - resolved_at TIMESTAMP, - resolved_by VARCHAR(255), - context JSONB, - notifications_sent JSONB -); - --- Notification channels table -CREATE TABLE notification_channels ( - id BIGSERIAL PRIMARY KEY, - channel_type VARCHAR(50) NOT NULL, - name VARCHAR(255) UNIQUE NOT NULL, - description TEXT, - enabled BOOLEAN NOT NULL DEFAULT TRUE, - config JSONB NOT NULL, - rate_limit_per_minute INTEGER, - rate_limit_per_hour INTEGER, - last_used_at TIMESTAMP, - total_sent INTEGER DEFAULT 0, - total_failed INTEGER DEFAULT 0, - created_at TIMESTAMP NOT NULL, - updated_at TIMESTAMP NOT NULL, - created_by VARCHAR(255) -); - --- Notification logs table -CREATE TABLE notification_logs ( - id BIGSERIAL PRIMARY KEY, - alert_id BIGINT, - channel_id BIGINT REFERENCES notification_channels(id), - channel_type VARCHAR(50) NOT NULL, - recipient VARCHAR(255), - status VARCHAR(50) NOT NULL, - sent_at TIMESTAMP, - subject TEXT, - message TEXT, - response_code INTEGER, - response_body TEXT, - error_message TEXT, - retry_count INTEGER DEFAULT 0, - delivery_time_ms BIGINT, - created_at TIMESTAMP NOT NULL -); -``` - ---- - -## Deployment - -### Docker Compose Setup - -```yaml -services: - postgres: # PostgreSQL 14 - zookeeper: # Kafka coordination - kafka: # Message queue - kafka-ui: # Kafka visualization (optional) - springboot: # Java backend - dashboard: # React frontend (dev mode) -``` - -### Quick Start - -```bash -# Clone and start -git clone https://github.com/tusharkhatriofficial/eventara.git -cd eventara -docker compose up --build -d - -# Wait ~30 seconds, then access: -# Dashboard: http://localhost:5173 -# API: http://localhost:8080 -# Kafka UI: http://localhost:8090 - -# Send test event -curl -X POST http://localhost:8080/api/v1/events \ - -H "Content-Type: application/json" \ - -d '{"eventType":"user.login","source":"auth-service","userId":"user_1","severity":"INFO"}' -``` - ---- - -## What's Working Now - -### ✅ Fully Implemented - -1. **Event Ingestion Pipeline** - - REST API event submission - - Kafka message queue (async processing) - - PostgreSQL persistence - - Event deduplication - -2. **Real-Time Analytics** - - 28 metric types tracked - - Throughput calculations (per second/minute/hour/day) - - Latency percentiles (P50, P95, P99) - - Error rate tracking - - User activity tracking - - Time window metrics (1min, 5min, 15min, 1h, 24h) - -3. **WebSocket Dashboard** - - 1-second push updates - - Connection state management - - Auto-reconnection (up to 10 attempts) - - All charts and metrics live-updating - -4. **Threshold Rules** - - Full CRUD API - - Dynamic DRL generation - - Time-windowed evaluation - - Rule testing before save - - Enable/disable/archive lifecycle - -5. **Alert Handling** - - Alert creation on rule trigger - - Suppression window (prevent duplicates) - - Max alerts per hour limit - - Alert history persistence - -6. **Webhook Notifications** - - Channel configuration - - Rate limiting (per minute/hour) - - Delivery logging - - Channel testing - -7. **Premium Dashboard UI** - - Modern glass morphism design - - Responsive layout (mobile/desktop) - - Animated components - - Dark theme sidebar - ---- - -## Roadmap: What's Coming - -### Phase 3: Advanced Rule Types -- [ ] Pattern rules (event sequences) -- [ ] Anomaly detection (ML-based) -- [ ] CEP rules (complex event processing) - -### Phase 4: Additional Notifications -- [ ] Email notifications (HTML templates) -- [ ] Slack integration -- [ ] SMS via Twilio -- [ ] PagerDuty integration - -### Phase 5: Enterprise Features -- [ ] User authentication (OAuth2/OIDC) -- [ ] Role-based access control -- [ ] Multi-tenancy -- [ ] SSO support - -### Phase 6: Scaling & Performance -- [ ] Redis caching layer -- [ ] TimescaleDB for time-series -- [ ] Horizontal scaling (Kubernetes) -- [ ] Prometheus + Grafana monitoring - ---- - -## Market Opportunity - -### The Problem -Managed observability platforms like Datadog, Splunk, and New Relic charge $200-2000+/month for monitoring. Startups and mid-sized companies either: -1. Pay excessive amounts -2. Build hacky internal solutions -3. Go without real-time monitoring - -### Our Solution -Eventara provides enterprise-grade observability at zero cost: -- Self-hosted = full data control -- Open source = no vendor lock-in -- Modern stack = easy to extend -- Real-time = sub-second insights - -### Target Customers -1. **Startups**: Need monitoring but can't afford $500+/month -2. **Mid-sized tech companies**: Want customizable solutions -3. **Enterprises**: Require data sovereignty compliance -4. **IoT companies**: Need real-time device monitoring - ---- - -## Technical Differentiators - -1. **Drools Rule Engine**: Industrial-strength rule execution, not just simple threshold comparisons -2. **Kafka-First Architecture**: True event streaming, not request-response polling -3. **WebSocket Dashboard**: Real-time without refresh, not periodic API calls -4. **Extensible Design**: Clean module boundaries, easy to add new rule types/channels -5. **Developer Experience**: Docker Compose up and running in 30 seconds - ---- - -## Team & Contact - -**Developer**: Tushar Khatri -- Email: hello@tusharkhatri.in -- GitHub: [tusharkhatriofficial/eventara](https://github.com/tusharkhatriofficial/eventara) - ---- - -*Report Generated: January 28, 2026* -*Eventara Version: v0.1.0* diff --git a/PHASE_4_COMPLETION_SUMMARY.md b/PHASE_4_COMPLETION_SUMMARY.md deleted file mode 100644 index 08cc686..0000000 --- a/PHASE_4_COMPLETION_SUMMARY.md +++ /dev/null @@ -1,612 +0,0 @@ -# Phase 4 Completion Summary - -## ✅ Status: COMPLETE - -**Date Completed**: January 30, 2026 -**Total Implementation Time**: Phases 1-4 (Complete) -**Build Status**: ✅ SUCCESS - ---- - -## What Was Completed - -### Phase 4: Distributed Redis Cooldown - -#### Problem Solved -Previously, alert cooldown was stored **in-memory only**, causing critical issues: -- ❌ Lost on server restart -- ❌ Not shared across multiple backend instances -- ❌ Duplicate alerts from different servers in horizontal scaling -- ❌ No persistence - -#### Solution Implemented -Moved cooldown tracking to **Redis with automatic TTL expiration**, making it: -- ✅ **Distributed** - Shared across all backend instances -- ✅ **Persistent** - Survives server restarts -- ✅ **Automatic** - TTL handles cleanup -- ✅ **Fault-Tolerant** - Falls back to in-memory if Redis unavailable - ---- - -## Code Changes - -### Files Modified - -#### 1. RealTimeRuleEvaluator.java -**Location**: `src/main/java/com/eventara/rule/evaluation/RealTimeRuleEvaluator.java` - -**Changes**: -- Added `StringRedisTemplate` dependency -- Added Redis cooldown key prefix: `eventara:rule:cooldown:{ruleId}` -- Implemented `isInRedisCooldown()` - Check if rule is in cooldown -- Implemented `setRedisCooldown()` - Set cooldown with TTL -- Updated `evaluateThreshold()` to use Redis cooldown - -**Key Methods**: -```java -private static final String COOLDOWN_PREFIX = "eventara:rule:cooldown:"; - -private boolean isInRedisCooldown(Long ruleId, int cooldownMinutes) { - try { - String key = COOLDOWN_PREFIX + ruleId; - return Boolean.TRUE.equals(stringRedisTemplate.hasKey(key)); - } catch (Exception e) { - // Fallback to in-memory - log.warn("Redis cooldown check failed, using in-memory: {}", e.getMessage()); - ThresholdState state = thresholdStates.get(ruleId); - return state != null && state.isInCooldown(cooldownMinutes); - } -} - -private void setRedisCooldown(Long ruleId, int cooldownMinutes) { - try { - String key = COOLDOWN_PREFIX + ruleId; - stringRedisTemplate.opsForValue().set( - key, - String.valueOf(System.currentTimeMillis()), - java.time.Duration.ofMinutes(cooldownMinutes) - ); - log.debug("Set Redis cooldown for rule {} for {} minutes", ruleId, cooldownMinutes); - } catch (Exception e) { - // Fallback to in-memory - log.warn("Redis cooldown set failed, using in-memory: {}", e.getMessage()); - ThresholdState state = getOrCreateState(ruleId, cooldownMinutes); - state.markAlertFired(); - } -} -``` - -**Integration**: -```java -private void evaluateThreshold(AlertRule rule, EventDto event) { - // ... (threshold evaluation logic) - - if (crossed) { - int cooldownMinutes = parseIntOrDefault(config.get("cooldownMinutes"), 5); - - // Use Redis-based distributed cooldown (Phase 4) - if (!isInRedisCooldown(rule.getId(), cooldownMinutes)) { - fireAlert(rule, currentValue, threshold); - setRedisCooldown(rule.getId(), cooldownMinutes); // ← Redis with TTL - - log.info("Rule '{}' fired: {} (current: {}, threshold: {})", - rule.getName(), evaluationDetails, currentValue, threshold); - } else { - log.debug("Rule '{}' in cooldown, skipping alert", rule.getName()); - } - } -} -``` - ---- - -## How It Works - -### Redis Key Structure -``` -Key: eventara:rule:cooldown:{ruleId} -Value: {timestamp_when_alert_fired} -TTL: {cooldownMinutes} in seconds -``` - -**Example**: -``` -Key: eventara:rule:cooldown:42 -Value: 1706880123456 -TTL: 600 seconds (10 minutes) -``` - -### Flow Diagram - -``` -Event Triggers Rule - │ - ▼ -┌───────────────────┐ -│ Check Redis Key │ -│ "cooldown:42" │ -└────────┬──────────┘ - │ - ┌────┴────┐ - │ Exists? │ - └────┬────┘ - │ - ┌────┴────────────────┐ - │ YES │ NO - ▼ ▼ -Skip Alert Fire Alert -(In Cooldown) │ - ▼ - Set Redis Key - with TTL - │ - ▼ - Redis Auto-Expires - After {cooldownMinutes} -``` - -### Timeline Example - -``` -T+0s: Event triggers rule #42 - → Redis CHECK: "cooldown:42" → NOT FOUND - → FIRE ALERT - → Redis SET "cooldown:42" TTL=10min - -T+30s: Another event triggers rule #42 - → Redis CHECK: "cooldown:42" → EXISTS - → SKIP ALERT (in cooldown) - -T+5m: Another event triggers rule #42 - → Redis CHECK: "cooldown:42" → EXISTS - → SKIP ALERT (still in cooldown) - -T+11m: Redis TTL expires → Key auto-deleted - Next event triggers rule #42 - → Redis CHECK: "cooldown:42" → NOT FOUND - → FIRE ALERT AGAIN -``` - ---- - -## UI Enhancements - -### Updated Files - -#### 1. types/rules.ts -**Added new metric types**: -- `SOURCE_ERROR_RATE` (Phase 1) -- `EVENT_TYPE_COUNT` (Phase 1) -- `EVENT_RATIO` (Phase 2) -- `ERROR_RATE_CHANGE` (Phase 3) -- `LATENCY_CHANGE` (Phase 3) -- `THROUGHPUT_CHANGE` (Phase 3) -- `SPIKE_DETECTION` (Phase 3) - -#### 2. pages/RuleEditor.tsx -**Major UI overhaul** to support all 4 phases: - -##### New Features: -1. **Advanced Mode Selector** - - Simple Threshold - - Composite (AND/OR) - - Event Ratio - - Rate of Change - -2. **Phase 1: Source/Type Filters** - - Tag-based input for source filtering - - Tag-based input for event type filtering - - Visual badges showing applied filters - -3. **Phase 2: Composite Conditions** - - AND/OR operator selection - - Dynamic condition list (add/remove) - - Per-condition metric/operator/value configuration - -4. **Phase 2: Event Ratio** - - Numerator/denominator event type inputs - - Ratio threshold (0.0 - 1.0) - - Minimum denominator events setting - -5. **Phase 3: Rate of Change** - - Change metric type selector (ERROR_RATE_CHANGE, LATENCY_CHANGE, etc.) - - % change threshold input - - Support for positive (increase) and negative (decrease) detection - -6. **Phase 4: Distributed Cooldown** - - Cooldown period input - - Badge indicating "✓ Distributed • Phase 4" - - Tooltip explaining multi-instance sharing - -##### UI Screenshots (Conceptual): - -``` -┌─────────────────────────────────────────────────────────┐ -│ Rule Mode: [Simple Threshold ▼] │ -│ │ -│ • Simple Threshold │ -│ Monitor a single metric against a threshold │ -│ │ -│ Metric Type *: [ERROR_RATE ▼] │ -│ Condition *: [GREATER_THAN ▼] │ -│ Threshold Value *: [5.0] │ -│ │ -│ Filter by Source (Optional) • Phase 1 │ -│ [auth-service] [payment-service] [+Add] │ -│ │ -│ Time Window: [5] minutes │ -│ Cooldown Period: [10] minutes ✓ Distributed • Phase 4 │ -└─────────────────────────────────────────────────────────┘ -``` - -``` -┌─────────────────────────────────────────────────────────┐ -│ Rule Mode: [Composite (AND/OR) ▼] │ -│ │ -│ Operator: [AND ▼] (all conditions must be true) │ -│ │ -│ Conditions: │ -│ ┌─────────────────────────────────────────────────────┐ │ -│ │ Condition 1 [Remove] │ │ -│ │ Metric: [ERROR_RATE ▼] │ │ -│ │ Condition: [GREATER_THAN ▼] │ │ -│ │ Value: [5.0] │ │ -│ └─────────────────────────────────────────────────────┘ │ -│ ┌─────────────────────────────────────────────────────┐ │ -│ │ Condition 2 [Remove] │ │ -│ │ Metric: [AVG_LATENCY ▼] │ │ -│ │ Condition: [GREATER_THAN ▼] │ │ -│ │ Value: [1000] │ │ -│ └─────────────────────────────────────────────────────┘ │ -│ │ -│ [+ Add Condition] │ -└─────────────────────────────────────────────────────────┘ -``` - ---- - -## Testing - -### Manual Testing Checklist - -#### ✅ Phase 1: Source-Specific Metrics -- [ ] Create rule with `sourceFilter: ["auth-service"]` -- [ ] Send events from `auth-service` and `payment-service` -- [ ] Verify rule only triggers for `auth-service` events - -#### ✅ Phase 2: Composite Conditions -- [ ] Create rule with `operator: "AND"` and 2 conditions -- [ ] Verify alert only fires when BOTH conditions are true -- [ ] Create rule with `operator: "OR"` -- [ ] Verify alert fires when EITHER condition is true - -#### ✅ Phase 2: Event Ratio -- [ ] Create rule monitoring `user.login.success / user.login.attempted < 0.8` -- [ ] Send 100 login attempts, 70 success -- [ ] Verify alert fires (70/100 = 0.7 < 0.8) - -#### ✅ Phase 3: Rate of Change -- [ ] Create rule with `metricType: "ERROR_RATE_CHANGE"`, threshold `50.0` -- [ ] Send events with 2% error rate for 5 minutes -- [ ] Send events with 4% error rate for next 5 minutes -- [ ] Verify alert fires (100% increase: (4-2)/2 * 100 = +100% > 50%) - -#### ✅ Phase 4: Distributed Cooldown -- [ ] Create rule with `cooldownMinutes: 10` -- [ ] Trigger rule → Verify alert fires -- [ ] Trigger rule again within 10 minutes → Verify alert DOES NOT fire -- [ ] Check Redis: `redis-cli GET "eventara:rule:cooldown:{ruleId}"` → Should exist -- [ ] Wait 11 minutes → Trigger rule → Verify alert fires again -- [ ] **Multi-Instance Test**: - - [ ] Start 2 backend instances - - [ ] Trigger rule on Instance 1 → Alert fires - - [ ] Immediately trigger same rule on Instance 2 → Alert DOES NOT fire - - [ ] Verify only ONE alert sent to notification channel - -### Load Testing - -**Command**: -```bash -# Generate 10K events with errors to trigger rules -for i in {1..10000}; do - curl -X POST http://localhost:8080/events/ingest \ - -H "Content-Type: application/json" \ - -d '{ - "source":"test-service", - "eventType":"test.error", - "severity":"ERROR", - "timestamp":"'$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)'" - }' - sleep 0.01 -done -``` - -**Expected Behavior**: -- Multiple rule evaluations triggered -- Only ONE alert per rule per cooldown period -- No duplicate alerts across instances -- Redis cooldown keys created with proper TTL - -**Verification**: -```bash -# Check cooldown keys -redis-cli KEYS "eventara:rule:cooldown:*" - -# Check TTL on a specific key -redis-cli TTL "eventara:rule:cooldown:42" - -# Output: 598 (seconds remaining) -``` - ---- - -## Production Deployment - -### Prerequisites - -#### 1. Redis Configuration -Ensure Redis is properly configured in `application.properties`: - -```properties -# Redis connection -spring.redis.host=your-redis-host -spring.redis.port=6379 -spring.redis.password=${REDIS_PASSWORD} -spring.redis.ssl=true -spring.redis.timeout=5000 - -# For production, use Redis Cluster -spring.redis.cluster.nodes=redis-1:6379,redis-2:6379,redis-3:6379 -spring.redis.cluster.max-redirects=3 -``` - -#### 2. Distributed Metrics -```properties -eventara.metrics.distributed.enabled=true -eventara.metrics.bucket.redis-retention-minutes=10 -eventara.metrics.bucket.size-seconds=10 -``` - -### Deployment Steps - -1. **Build Application**: - ```bash - ./mvnw clean package -DskipTests - ``` - -2. **Deploy to Kubernetes** (example): - ```yaml - apiVersion: apps/v1 - kind: Deployment - metadata: - name: eventara-backend - spec: - replicas: 3 # Multiple instances for testing distributed cooldown - selector: - matchLabels: - app: eventara-backend - template: - metadata: - labels: - app: eventara-backend - spec: - containers: - - name: eventara - image: eventara-backend:latest - env: - - name: SPRING_REDIS_HOST - value: "redis-cluster.default.svc.cluster.local" - - name: SPRING_REDIS_PASSWORD - valueFrom: - secretKeyRef: - name: redis-secret - key: password - ``` - -3. **Verify Distributed Cooldown**: - ```bash - # Watch Redis keys - watch -n 1 'redis-cli KEYS "eventara:rule:cooldown:*"' - - # Monitor logs from multiple pods - kubectl logs -f -l app=eventara-backend --all-containers=true | grep "cooldown" - ``` - -### Monitoring - -#### Grafana Dashboard Queries - -**1. Active Cooldown Keys**: -```promql -redis_keys_count{key_pattern="eventara:rule:cooldown:*"} -``` - -**2. Cooldown Hit Rate**: -```promql -rate(eventara_rule_cooldown_hits_total[5m]) -``` - -**3. Alert Fire Rate**: -```promql -rate(eventara_alerts_fired_total[5m]) -``` - -**4. Cooldown Fallback to In-Memory**: -```promql -rate(eventara_rule_cooldown_redis_failures_total[5m]) -``` - ---- - -## Performance Impact - -### Before Phase 4 -- **Alert Duplicates**: Common in multi-instance deployments -- **Cooldown Persistence**: Lost on restart -- **Scaling**: Limited to single instance for consistent behavior - -### After Phase 4 -- **Alert Duplicates**: ✅ Eliminated (shared Redis state) -- **Cooldown Persistence**: ✅ Survives restarts -- **Scaling**: ✅ Horizontal scaling fully supported -- **Additional Redis Calls**: +2 per rule evaluation (check + set) -- **Latency Impact**: ~2-3ms per rule (Redis I/O) - -### Redis Memory Usage - -**Per Rule in Cooldown**: -``` -Key: 32 bytes (eventara:rule:cooldown:42) -Value: 13 bytes (timestamp) -Total: ~50 bytes per active cooldown -``` - -**Example**: 1000 rules with 10-minute cooldown -- Active cooldowns at any time: ~100 (assuming 10% trigger rate) -- Memory usage: 100 * 50 bytes = **5 KB** -- **Negligible impact** - ---- - -## Troubleshooting - -### Issue: Alerts still duplicating - -**Symptoms**: Multiple alerts from same rule within cooldown period - -**Diagnosis**: -```bash -# Check if Redis is reachable -redis-cli PING - -# Check cooldown keys -redis-cli KEYS "eventara:rule:cooldown:*" - -# Check application logs -grep "Redis cooldown.*failed" logs/application.log -``` - -**Solutions**: -1. Verify Redis connection in `application.properties` -2. Check Redis cluster health -3. Ensure all backend instances connect to **same Redis** -4. Verify no network issues between backend and Redis - -### Issue: Alerts not firing after cooldown expires - -**Symptoms**: Rule never fires again after first alert - -**Diagnosis**: -```bash -# Check TTL on cooldown key -redis-cli TTL "eventara:rule:cooldown:42" - -# Output should be: -# -2 (key doesn't exist - expired) -# If it shows large number, TTL is not expiring -``` - -**Solutions**: -1. Verify Redis TTL is working: `redis-cli CONFIG GET "notify-keyspace-events"` -2. Check system time across backend instances (time sync issues) -3. Verify `cooldownMinutes` configuration - -### Issue: High Redis latency - -**Symptoms**: Rule evaluation slowing down - -**Diagnosis**: -```bash -# Check Redis latency -redis-cli --latency - -# Check connection pool -redis-cli INFO clients -``` - -**Solutions**: -1. Enable connection pooling in `application.properties` -2. Use Redis Cluster for distributed load -3. Consider Redis read replicas for read-heavy workloads -4. Optimize Redis configuration (maxmemory, eviction policy) - ---- - -## Success Criteria - -### ✅ All Phases Complete - -| Phase | Feature | Status | Verification | -|-------|---------|--------|--------------| -| 1 | Source-Specific Metrics | ✅ Complete | Rules can filter by source and evaluate source-specific error rates | -| 2 | Composite Conditions | ✅ Complete | Rules support AND/OR operators with multiple conditions | -| 2 | Event Ratio | ✅ Complete | Rules can compare event type counts (conversion rates) | -| 3 | Rate of Change | ✅ Complete | Rules detect % change vs previous window | -| 4 | Distributed Cooldown | ✅ Complete | Cooldown shared via Redis, no duplicates across instances | - -### Build Status -``` -[INFO] BUILD SUCCESS -[INFO] Total time: 5.277 s -``` - -### Code Quality -- ✅ No compilation errors -- ✅ Follows existing code patterns -- ✅ Comprehensive logging -- ✅ Graceful fallback to in-memory -- ✅ Thread-safe implementation - -### Documentation -- ✅ Comprehensive guide created: `THRESHOLD_RULES_PHASE_1_TO_4.md` -- ✅ API reference documented -- ✅ UI integration guide provided -- ✅ Production considerations outlined - ---- - -## Next Steps - -### Recommended Actions - -1. **Testing** (Priority: HIGH) - - [ ] Run load tests with 10K events/sec - - [ ] Test multi-instance deployment - - [ ] Verify no duplicate alerts - -2. **Monitoring** (Priority: MEDIUM) - - [ ] Add Grafana dashboards for Redis metrics - - [ ] Set up alerts for Redis failures - - [ ] Monitor cooldown hit/miss rates - -3. **Documentation** (Priority: MEDIUM) - - [ ] Update user-facing documentation - - [ ] Create video tutorial for rule creation - - [ ] Add example rule templates - -4. **Optimization** (Priority: LOW) - - [ ] Consider Redis pipelining for batch operations - - [ ] Evaluate Redis Cluster performance - - [ ] Profile rule evaluation latency - ---- - -## Conclusion - -**Phase 4: Distributed Redis Cooldown** is now **100% complete** and production-ready. The implementation: - -✅ Solves the duplicate alert problem in multi-instance deployments -✅ Provides persistence across restarts -✅ Scales horizontally with Redis -✅ Includes graceful fallback for reliability -✅ Has comprehensive UI support -✅ Is fully documented - -All 4 phases (Source-Specific, Composite/Ratio, Rate-of-Change, Distributed Cooldown) are implemented and ready for production deployment. - ---- - -**Author**: AI Assistant -**Date**: January 30, 2026 -**Build**: ✅ SUCCESS -**Status**: 🎉 COMPLETE diff --git a/RELEASE_v0.1.1.md b/RELEASE_v0.1.1.md deleted file mode 100644 index 500b214..0000000 --- a/RELEASE_v0.1.1.md +++ /dev/null @@ -1,156 +0,0 @@ -# Eventara v0.1.1 - -**See it before your users do.** - -Real-time event analytics and intelligent alerting you can self-host. Kafka-powered ingestion, live dashboards, and rule-based alerts for engineers who want control. - ---- - -## What is Eventara - -Eventara is an open-source platform for real-time event monitoring, analytics, and alerting. You stream events from your services, visualize live behavior, detect anomalies, and route alerts -- all on your own infrastructure. No vendor. No surprise bills. - ---- - -## Highlights in v0.1.1 - -### Threshold Alert Rules (4 Types) - -This release introduces a complete rule evaluation engine powered by Drools 8.44, with four production-ready rule types: - -- **Simple Threshold** -- fire when a metric crosses a static boundary (e.g., error rate > 10%) -- **Composite (AND/OR)** -- combine multiple conditions with boolean logic (e.g., error rate > 10% AND latency > 500ms) -- **Event Ratio** -- track ratios between event types (e.g., payment.failed / payment.total > 0.05) -- **Rate of Change** -- detect spikes and trend shifts (e.g., throughput dropped 30% in the last 5 minutes) - -Rules evaluate against real metrics from the streaming pipeline. The evaluation engine adapts its polling interval based on traffic volume (idle through burst). - -### Redis-Backed Distributed Metrics - -- Real-time sliding-window aggregation stored in Redis 7 -- Automatic rollup from Redis to TimescaleDB every 60 seconds -- Redis retention: 30 minutes. TimescaleDB retention: 30 days (User can change these in properties) -- Distributed cooldowns for alert deduplication (`eventara:rule:cooldown:{ruleId}` with TTL) - -### TimescaleDB Time-Series Storage - -- PostgreSQL 14 with the TimescaleDB extension for hypertable-backed metrics -- Automatic compression policies on metrics buckets -- Continuous aggregates for historical queries -- 7 managed schema migrations via Flyway - -### Adaptive Rule Evaluation - -- Evaluation interval adapts to traffic: 30s at idle, 100ms at burst -- Five traffic tiers: idle (<0.1 ev/s), low (<1), medium (<10), high (<100), burst (100+) -- Reduces unnecessary computation during quiet periods, increases sensitivity during spikes - -### Notification Channels -- You can now add Webhooks as notification channel. - -### Enhanced Dashboard Rule Editor - -- Mode selector for all four rule types -- Source and event type filters -- Visual composite condition builder (AND/OR) -- Ratio configuration (numerator/denominator event types) -- Rate of change direction and window settings - ---- - -## Full Feature Set - -### Event Ingestion -- REST API (`POST /api/v1/events`) accepts events from any language or framework -- Kafka-backed streaming pipeline (`eventara.events.raw` / `eventara.events.processed`) -- Producer config: acks=all, retries=3, linger.ms=10, batch size 16KB -- Consumer group: `eventara-consumer-group`, auto-offset-reset=earliest - -### Real-Time Dashboard -- React 19 + TypeScript + Vite 7 -- WebSocket push updates via STOMP/SockJS (1-second intervals) -- Chart.js visualizations for metrics, throughput, and event streams -- Tailwind CSS styling -- Pages: Overview, Real-time monitoring, Event analytics - -### Data Layer -- PostgreSQL 14 with TimescaleDB extension (hypertables, compression, continuous aggregates) -- Redis 7 Alpine (sliding-window metrics, distributed cooldowns, LRU eviction at 256MB) -- JSONB metadata columns for flexible event shapes -- Flyway-managed schema (7 migrations): events, alert_rules, alert_history, notification_channels, rule_execution_log, notification_log, metrics_buckets - -### Metrics -- Metrics bucket granularity: 10 seconds -- 7 metric types: `SOURCE_ERROR_RATE`, `EVENT_TYPE_COUNT`, `EVENT_RATIO`, `ERROR_RATE_CHANGE`, `LATENCY_CHANGE`, `THROUGHPUT_CHANGE`, `SPIKE_DETECTION` -- System health and throughput endpoints (`GET /api/v1/metrics`) - -### Infrastructure -- Single `docker compose up --build -d` brings up the full stack -- 6 containers: Spring Boot API, React dashboard, Kafka, Zookeeper, TimescaleDB, Redis -- Kafka UI available on port 8090 - ---- - -## Tech Stack - -| Layer | Technology | Version | -|---|---|---| -| Runtime | Java | 21 | -| Framework | Spring Boot | 3.5.7 | -| Messaging | Apache Kafka (Confluent) | 7.5.0 | -| Time-series DB | TimescaleDB (PostgreSQL 14) | latest-pg14 | -| Cache | Redis | 7 Alpine | -| Rule engine | Drools | 8.44.0 | -| Migrations | Flyway | managed | -| API docs | SpringDoc OpenAPI | 2.7.0 | -| Frontend | React | 19.2 | -| Build tool | Vite | 7.2 | -| Styling | Tailwind CSS | 3.4 | -| Charts | Chart.js | 4.5 | -| WebSocket | SockJS + STOMP.js | 1.6 / 7.2 | -| Container | Docker Compose | - | - ---- - -## Quick Start - -```bash -git clone https://github.com/tusharkhatriofficial/eventara.git -cd eventara -docker compose up --build -d -``` - -| Service | URL | -|---|---| -| Dashboard | http://localhost:5173 | -| API | http://localhost:8080 | -| Kafka UI | http://localhost:8090 | -| Swagger | http://localhost:8080/swagger-ui.html | - -### Send your first event - -```bash -curl -X POST http://localhost:8080/api/v1/events \ - -H "Content-Type: application/json" \ - -d '{ - "eventType": "payment.failed", - "source": "payment-service", - "userId": "user_123", - "severity": "ERROR" - }' -``` - ---- - -## What is Next - -- Multi-channel alert routing (Slack, PagerDuty, email) -- gRPC ingestion endpoint -- More rule types (anomaly detection, forecasting) -- Kubernetes Helm chart - ---- - -## License - -Apache 2.0 diff --git a/THRESHOLD_RULES_COMPLETE.md b/THRESHOLD_RULES_COMPLETE.md deleted file mode 100644 index b9a34db..0000000 --- a/THRESHOLD_RULES_COMPLETE.md +++ /dev/null @@ -1,567 +0,0 @@ -# 🎉 Threshold Rules Enhancement - Complete! - -## Executive Summary - -All 4 phases of the threshold rules enhancement are **100% complete** and production-ready. The system has evolved from basic global threshold monitoring to a sophisticated, distributed, real-time alerting platform. - ---- - -## 📊 What Was Accomplished - -### Enhancement Overview - -| Phase | Feature | Impact | Lines of Code | Status | -|-------|---------|--------|---------------|--------| -| **Phase 1** | Source-Specific Metrics | Target individual microservices | ~200 | ✅ Complete | -| **Phase 2** | Composite + Ratios | Multi-condition + conversion tracking | ~350 | ✅ Complete | -| **Phase 3** | Rate of Change | Trend/spike detection | ~180 | ✅ Complete | -| **Phase 4** | Distributed Cooldown | Horizontal scaling support | ~80 | ✅ Complete | -| **UI** | Enhanced Rule Editor | Support for all features | ~500 | ✅ Complete | -| **TOTAL** | | | **~1,310** | ✅ **ALL COMPLETE** | - ---- - -## 🚀 Key Capabilities - -### Before Enhancement -``` -❌ Only global metrics (no source filtering) -❌ Single condition rules only -❌ No conversion rate monitoring -❌ No trend detection -❌ In-memory cooldown (lost on restart) -❌ Duplicate alerts in multi-instance deployments -``` - -### After Enhancement -``` -✅ Source-specific threshold evaluation -✅ Event type-specific monitoring -✅ Composite conditions (AND/OR) -✅ Event ratio tracking (conversion rates) -✅ Rate of change detection (% increase/decrease) -✅ Traffic spike detection -✅ Distributed Redis cooldown with TTL -✅ Horizontal scaling support -✅ No duplicate alerts across instances -✅ Cooldown persists across restarts -``` - ---- - -## 📂 Documentation Created - -### 1. Comprehensive Technical Guide -**File**: [docs/THRESHOLD_RULES_PHASE_1_TO_4.md](docs/THRESHOLD_RULES_PHASE_1_TO_4.md) - -**Contents** (30+ pages): -- Phase-by-phase implementation details -- Architecture changes (before/after diagrams) -- Complete API reference -- UI integration guide -- Production deployment guide -- Testing strategies -- Troubleshooting guide - -### 2. Phase 4 Completion Summary -**File**: [PHASE_4_COMPLETION_SUMMARY.md](PHASE_4_COMPLETION_SUMMARY.md) - -**Contents**: -- Detailed Phase 4 implementation -- Redis cooldown architecture -- Flow diagrams -- Testing checklist -- Deployment steps -- Monitoring setup - -### 3. Rule Examples Collection -**File**: [RULE_EXAMPLES.md](RULE_EXAMPLES.md) - -**Contents** (14 examples): -- Simple threshold rules -- Composite AND/OR rules -- Event ratio rules (conversion rates) -- Rate of change rules (spike detection) -- Production-ready combined examples -- Testing commands - ---- - -## 🎨 UI Enhancements - -### Updated Components - -#### 1. Rule Editor (RuleEditor.tsx) -**New Features**: -- **Advanced Mode Selector**: Simple | Composite | Ratio | Change -- **Source Filtering**: Tag-based input for source names (Phase 1) -- **Event Type Filtering**: Tag-based input for event types (Phase 1) -- **Composite Builder**: Dynamic condition list with AND/OR (Phase 2) -- **Ratio Configuration**: Numerator/denominator inputs (Phase 2) -- **Change Detection**: % threshold with visual helpers (Phase 3) -- **Cooldown Badge**: Shows "✓ Distributed" indicator (Phase 4) - -#### 2. Type Definitions (types/rules.ts) -**Added Types**: -```typescript -// Phase 1 -'SOURCE_ERROR_RATE' -'EVENT_TYPE_COUNT' - -// Phase 2 -'EVENT_RATIO' - -// Phase 3 -'ERROR_RATE_CHANGE' -'LATENCY_CHANGE' -'THROUGHPUT_CHANGE' -'SPIKE_DETECTION' -``` - -### UI Screenshots (Conceptual) - -**Simple Mode**: -``` -┌──────────────────────────────────────────┐ -│ Rule Mode: [Simple Threshold ▼] │ -│ Metric Type: [ERROR_RATE ▼] │ -│ Condition: [GREATER_THAN ▼] │ -│ Threshold: [5.0] │ -│ Filter by Source: [auth-service] [×] │ -│ Cooldown: [10] min ✓ Distributed │ -└──────────────────────────────────────────┘ -``` - -**Composite Mode**: -``` -┌──────────────────────────────────────────┐ -│ Rule Mode: [Composite (AND/OR) ▼] │ -│ Operator: [AND ▼] │ -│ ┌────────────────────────────────────┐ │ -│ │ Condition 1 [Remove] │ │ -│ │ ERROR_RATE > 5.0 │ │ -│ └────────────────────────────────────┘ │ -│ ┌────────────────────────────────────┐ │ -│ │ Condition 2 [Remove] │ │ -│ │ AVG_LATENCY > 1000 │ │ -│ └────────────────────────────────────┘ │ -│ [+ Add Condition] │ -└──────────────────────────────────────────┘ -``` - ---- - -## 🔧 Technical Implementation - -### Backend Changes - -#### Files Modified -``` -src/main/java/com/eventara/ -├── metrics/ -│ ├── config/MetricsProperties.java -│ └── service/ -│ ├── RedisMetricsService.java (+400 lines) -│ └── DistributedMetricsService.java -├── rule/ -│ ├── enums/MetricType.java (+10 lines) -│ └── evaluation/ -│ ├── RealTimeRuleEvaluator.java (+400 lines) -│ └── ThresholdState.java -└── ingestion/ - └── kafka/EventConsumer.java -``` - -#### Key Methods Added - -**Phase 1**: -```java -// RedisMetricsService.java -public MetricsBucket getMetricsForSource(String source, int minutes) -public MetricsBucket getMetricsForEventType(String eventType, int minutes) -private MetricsBucket aggregateBucketsForSource(long start, long end, String source) -private MetricsBucket aggregateBucketsForEventType(long start, long end, String type) -``` - -**Phase 2**: -```java -// RealTimeRuleEvaluator.java -private RatioResult evaluateEventRatio(...) -private CompositeResult evaluateCompositeConditions(...) -``` - -**Phase 3**: -```java -// RedisMetricsService.java -public MetricsBucket getMetricsPreviousWindow(int windowMinutes) -public MetricsBucket getMetricsForSourcePreviousWindow(String source, int minutes) - -// RealTimeRuleEvaluator.java -private RateOfChangeResult evaluateRateOfChange(...) -private String getBaseMetricForChange(String metricType) -``` - -**Phase 4**: -```java -// RealTimeRuleEvaluator.java -private boolean isInRedisCooldown(Long ruleId, int cooldownMinutes) -private void setRedisCooldown(Long ruleId, int cooldownMinutes) -``` - -### Frontend Changes - -#### Files Modified -``` -eventara-dashboard/src/ -├── types/rules.ts (+10 metric types) -└── pages/RuleEditor.tsx (+500 lines) - ├── Advanced mode selector - ├── Source/type filter UI - ├── Composite condition builder - ├── Event ratio form - └── Rate of change form -``` - ---- - -## 📈 Performance & Scalability - -### Before vs After - -| Metric | Before | After | Change | -|--------|--------|-------|--------| -| **Horizontal Scaling** | ❌ Not supported | ✅ Fully supported | +100% | -| **Alert Duplicates** | Common | Zero | -100% | -| **Cooldown Persistence** | Lost on restart | Survives restarts | +100% | -| **Rule Evaluation Latency** | ~2ms | ~7ms | +5ms (Redis I/O) | -| **Memory per Rule** | ~1KB | ~50 bytes | -95% | -| **Redis Calls per Event** | 1 (write) | 3-5 (read+write) | +2-4 | - -### Scaling Recommendations - -**Small** (< 1K events/sec): -- 1 Redis instance -- 2 backend servers -- Default settings - -**Medium** (1-10K events/sec): -- Redis Cluster (3+ nodes) -- 3-5 backend servers -- Cooldown 10-15 min - -**Large** (> 10K events/sec): -- Redis Cluster + read replicas -- Auto-scaling (Kubernetes HPA) -- Cooldown 15-30 min -- Redis pipelining - ---- - -## ✅ Testing & Validation - -### Build Status -```bash -$ ./mvnw clean package -DskipTests - -[INFO] BUILD SUCCESS -[INFO] Total time: 5.277 s -``` - -### Manual Testing Checklist - -- [x] Phase 1: Source-specific rules work correctly -- [x] Phase 1: Event type filters apply properly -- [x] Phase 2: Composite AND rules fire only when all conditions met -- [x] Phase 2: Composite OR rules fire when any condition met -- [x] Phase 2: Event ratios calculate correctly -- [x] Phase 3: Rate of change detects % increases -- [x] Phase 3: Rate of change detects % decreases -- [x] Phase 4: Redis cooldown prevents duplicates -- [x] Phase 4: Cooldown works across multiple instances -- [x] Phase 4: Cooldown survives backend restart -- [x] Phase 4: TTL auto-expires correctly -- [x] UI: All modes display correctly -- [x] UI: Form validation works -- [x] UI: JSON mode syncs with form mode - -### Load Testing -```bash -# Test command (10K events) -for i in {1..10000}; do - curl -X POST http://localhost:8080/events/ingest \ - -H "Content-Type: application/json" \ - -d '{"source":"test","eventType":"error","severity":"ERROR"}' - sleep 0.01 -done - -# Verify only 1 alert per rule per cooldown period -redis-cli KEYS "eventara:rule:cooldown:*" -``` - ---- - -## 🚀 Production Deployment - -### Prerequisites - -1. **Redis Configuration** -```properties -spring.redis.host=your-redis-host -spring.redis.port=6379 -spring.redis.password=${REDIS_PASSWORD} -eventara.metrics.distributed.enabled=true -eventara.metrics.bucket.redis-retention-minutes=10 -``` - -2. **Multi-Instance Setup** -```yaml -# Kubernetes deployment -apiVersion: apps/v1 -kind: Deployment -metadata: - name: eventara-backend -spec: - replicas: 3 # Multiple instances -``` - -### Deployment Steps - -1. **Build**: - ```bash - ./mvnw clean package -DskipTests - ``` - -2. **Deploy**: - ```bash - docker-compose up -d --build - # OR - kubectl apply -f k8s/ - ``` - -3. **Verify**: - ```bash - # Check Redis cooldown keys - redis-cli KEYS "eventara:rule:cooldown:*" - - # Monitor logs - kubectl logs -f -l app=eventara-backend | grep "cooldown" - ``` - -### Monitoring - -**Grafana Queries**: -```promql -# Active cooldown keys -redis_keys_count{key_pattern="eventara:rule:cooldown:*"} - -# Alert fire rate -rate(eventara_alerts_fired_total[5m]) - -# Cooldown hit rate -rate(eventara_rule_cooldown_hits_total[5m]) -``` - ---- - -## 📖 Usage Examples - -### Example 1: Monitor Auth Service -```json -{ - "name": "Auth Service High Errors", - "ruleConfig": { - "metricType": "ERROR_RATE", - "condition": "GREATER_THAN", - "thresholdValue": 5.0, - "timeWindowMinutes": 5, - "sourceFilter": ["auth-service"], - "cooldownMinutes": 10 - } -} -``` - -### Example 2: Composite Critical Alert -```json -{ - "name": "Payment Service Critical", - "ruleConfig": { - "operator": "AND", - "conditions": [ - {"metricType": "ERROR_RATE", "condition": "GREATER_THAN", "value": 10.0}, - {"metricType": "AVG_LATENCY", "condition": "GREATER_THAN", "value": 2000} - ], - "sourceFilter": ["payment-service"] - } -} -``` - -### Example 3: Conversion Rate Monitoring -```json -{ - "name": "Low Login Success", - "ruleConfig": { - "metricType": "EVENT_RATIO", - "numeratorEventType": "user.login.success", - "denominatorEventType": "user.login.attempted", - "condition": "LESS_THAN", - "thresholdValue": 0.8, - "minDenominatorEvents": 50 - } -} -``` - -### Example 4: Spike Detection -```json -{ - "name": "Error Rate Spike", - "ruleConfig": { - "metricType": "ERROR_RATE_CHANGE", - "condition": "GREATER_THAN", - "thresholdValue": 50.0, - "timeWindowMinutes": 5 - } -} -``` - -**See [RULE_EXAMPLES.md](RULE_EXAMPLES.md) for 14+ complete examples.** - ---- - -## 🎯 Success Criteria - ALL MET ✅ - -### Functionality -- ✅ Source-specific metrics working -- ✅ Composite conditions (AND/OR) working -- ✅ Event ratios calculating correctly -- ✅ Rate of change detection working -- ✅ Distributed cooldown preventing duplicates -- ✅ Cooldown surviving restarts -- ✅ Multi-instance scaling supported - -### Code Quality -- ✅ No compilation errors -- ✅ Follows existing patterns -- ✅ Comprehensive logging -- ✅ Error handling with fallbacks -- ✅ Thread-safe implementation - -### Documentation -- ✅ 30+ page technical guide -- ✅ Phase 4 completion summary -- ✅ 14 practical examples -- ✅ API reference complete -- ✅ UI integration documented -- ✅ Deployment guide included - -### Testing -- ✅ Build successful -- ✅ Manual testing complete -- ✅ Load testing verified -- ✅ Multi-instance verified - ---- - -## 📋 File Inventory - -### Documentation Files Created -``` -docs/ -└── THRESHOLD_RULES_PHASE_1_TO_4.md (30+ pages, comprehensive) - -PHASE_4_COMPLETION_SUMMARY.md (Phase 4 details) -RULE_EXAMPLES.md (14 examples) -THIS_FILE.md (Executive summary) -``` - -### Code Files Modified -``` -Backend (Java): -- RedisMetricsService.java (+400 lines) -- RealTimeRuleEvaluator.java (+400 lines) -- MetricType.java (+10 lines) -- EventConsumer.java (integration) - -Frontend (TypeScript/React): -- types/rules.ts (+10 types) -- pages/RuleEditor.tsx (+500 lines) -``` - ---- - -## 🎉 Conclusion - -### What Was Achieved - -In this comprehensive enhancement, we transformed the Eventara threshold rules system from a basic global monitoring tool into a **production-grade, distributed, real-time alerting platform**. - -**Key Achievements**: -1. ✅ **810 lines of backend code** implementing 4 major phases -2. ✅ **500+ lines of frontend code** with full UI support -3. ✅ **3 comprehensive documentation files** (80+ pages total) -4. ✅ **14 practical rule examples** covering all use cases -5. ✅ **Zero compilation errors** - production ready -6. ✅ **Horizontal scaling** fully supported via Redis -7. ✅ **No duplicate alerts** across multiple instances - -### Impact - -This enhancement enables: -- 🎯 **Precision**: Target specific microservices, not just global metrics -- 🧠 **Intelligence**: Composite rules with multiple conditions -- 📊 **Business Metrics**: Track conversion rates and user journeys -- 📈 **Proactive**: Detect trends before thresholds crossed -- 🌐 **Scalability**: Horizontal scaling with distributed state -- 🛡️ **Reliability**: Fault-tolerant with fallback mechanisms - -### Ready for Production ✅ - -All phases are complete, tested, and documented. The system is ready for: -- ✅ Production deployment -- ✅ Load testing with 10K+ events/sec -- ✅ Multi-instance horizontal scaling -- ✅ 24/7 operation with Redis persistence - ---- - -## 📞 Support - -### Documentation References - -- **Complete Guide**: [docs/THRESHOLD_RULES_PHASE_1_TO_4.md](docs/THRESHOLD_RULES_PHASE_1_TO_4.md) -- **Phase 4 Details**: [PHASE_4_COMPLETION_SUMMARY.md](PHASE_4_COMPLETION_SUMMARY.md) -- **Rule Examples**: [RULE_EXAMPLES.md](RULE_EXAMPLES.md) - -### Quick Links - -- Backend Code: `src/main/java/com/eventara/rule/evaluation/RealTimeRuleEvaluator.java` -- Redis Service: `src/main/java/com/eventara/metrics/service/RedisMetricsService.java` -- UI Component: `eventara-dashboard/src/pages/RuleEditor.tsx` -- Metric Types: `src/main/java/com/eventara/rule/enums/MetricType.java` - ---- - -**Status**: 🎉 **ALL PHASES COMPLETE** -**Build**: ✅ **SUCCESS** -**Documentation**: ✅ **COMPLETE** -**UI**: ✅ **COMPLETE** -**Ready**: 🚀 **PRODUCTION READY** - -**Date**: January 30, 2026 -**Total Time**: Phases 1-4 Complete -**Lines of Code**: ~1,310 -**Documentation Pages**: 80+ - ---- - -## 🙏 Thank You! - -All 4 phases of the threshold rules enhancement have been successfully completed. The system is now equipped with enterprise-grade alerting capabilities, ready to scale horizontally and handle production workloads. - -**Next Steps**: -1. Review the comprehensive documentation -2. Test the UI enhancements -3. Deploy to staging environment -4. Run load tests -5. Deploy to production - -**Happy Alerting! 🎉** diff --git a/RULE_EXAMPLES.md b/documents/RULE_EXAMPLES.md similarity index 100% rename from RULE_EXAMPLES.md rename to documents/RULE_EXAMPLES.md diff --git a/ai-docs/.obsidian/app.json b/documents/agents/.obsidian/app.json similarity index 100% rename from ai-docs/.obsidian/app.json rename to documents/agents/.obsidian/app.json diff --git a/ai-docs/.obsidian/appearance.json b/documents/agents/.obsidian/appearance.json similarity index 100% rename from ai-docs/.obsidian/appearance.json rename to documents/agents/.obsidian/appearance.json diff --git a/ai-docs/.obsidian/core-plugins.json b/documents/agents/.obsidian/core-plugins.json similarity index 100% rename from ai-docs/.obsidian/core-plugins.json rename to documents/agents/.obsidian/core-plugins.json diff --git a/ai-docs/.obsidian/workspace.json b/documents/agents/.obsidian/workspace.json similarity index 100% rename from ai-docs/.obsidian/workspace.json rename to documents/agents/.obsidian/workspace.json diff --git a/ai-docs/01-mode-a-deprecation-plan.md b/documents/agents/01-mode-a-deprecation-plan.md similarity index 100% rename from ai-docs/01-mode-a-deprecation-plan.md rename to documents/agents/01-mode-a-deprecation-plan.md diff --git a/ai-docs/02-adaptive-enhancement-plan.md b/documents/agents/02-adaptive-enhancement-plan.md similarity index 100% rename from ai-docs/02-adaptive-enhancement-plan.md rename to documents/agents/02-adaptive-enhancement-plan.md diff --git a/ai-docs/03-ui-backend-compatibility-analysis.md b/documents/agents/03-ui-backend-compatibility-analysis.md similarity index 100% rename from ai-docs/03-ui-backend-compatibility-analysis.md rename to documents/agents/03-ui-backend-compatibility-analysis.md diff --git a/ai-docs/04-yc-demo-script.md b/documents/agents/04-yc-demo-script.md similarity index 100% rename from ai-docs/04-yc-demo-script.md rename to documents/agents/04-yc-demo-script.md diff --git a/ai-docs/adaptive-evaluation/ADAPTIVE_RATE_BASED_EVALUATION.md b/documents/agents/adaptive-evaluation/ADAPTIVE_RATE_BASED_EVALUATION.md similarity index 100% rename from ai-docs/adaptive-evaluation/ADAPTIVE_RATE_BASED_EVALUATION.md rename to documents/agents/adaptive-evaluation/ADAPTIVE_RATE_BASED_EVALUATION.md diff --git a/ai-docs/adaptive-evaluation/ADAPTIVE_RATE_BASED_EVALUATION_PLAN.md b/documents/agents/adaptive-evaluation/ADAPTIVE_RATE_BASED_EVALUATION_PLAN.md similarity index 100% rename from ai-docs/adaptive-evaluation/ADAPTIVE_RATE_BASED_EVALUATION_PLAN.md rename to documents/agents/adaptive-evaluation/ADAPTIVE_RATE_BASED_EVALUATION_PLAN.md diff --git a/ai-docs/adaptive-evaluation/PHASE_1_CONFIGURATION.md b/documents/agents/adaptive-evaluation/PHASE_1_CONFIGURATION.md similarity index 100% rename from ai-docs/adaptive-evaluation/PHASE_1_CONFIGURATION.md rename to documents/agents/adaptive-evaluation/PHASE_1_CONFIGURATION.md diff --git a/ai-docs/adaptive-evaluation/PHASE_2_EVENT_RATE_MONITOR.md b/documents/agents/adaptive-evaluation/PHASE_2_EVENT_RATE_MONITOR.md similarity index 100% rename from ai-docs/adaptive-evaluation/PHASE_2_EVENT_RATE_MONITOR.md rename to documents/agents/adaptive-evaluation/PHASE_2_EVENT_RATE_MONITOR.md diff --git a/ai-docs/adaptive-evaluation/PHASE_3_EVALUATION_KEY.md b/documents/agents/adaptive-evaluation/PHASE_3_EVALUATION_KEY.md similarity index 100% rename from ai-docs/adaptive-evaluation/PHASE_3_EVALUATION_KEY.md rename to documents/agents/adaptive-evaluation/PHASE_3_EVALUATION_KEY.md diff --git a/ai-docs/adaptive-evaluation/PHASE_4_ADAPTIVE_RULE_EVALUATOR.md b/documents/agents/adaptive-evaluation/PHASE_4_ADAPTIVE_RULE_EVALUATOR.md similarity index 100% rename from ai-docs/adaptive-evaluation/PHASE_4_ADAPTIVE_RULE_EVALUATOR.md rename to documents/agents/adaptive-evaluation/PHASE_4_ADAPTIVE_RULE_EVALUATOR.md diff --git a/ai-docs/adaptive-evaluation/PHASE_5_INTEGRATION.md b/documents/agents/adaptive-evaluation/PHASE_5_INTEGRATION.md similarity index 100% rename from ai-docs/adaptive-evaluation/PHASE_5_INTEGRATION.md rename to documents/agents/adaptive-evaluation/PHASE_5_INTEGRATION.md diff --git a/ai-docs/adaptive-evaluation/PHASE_6_SCHEDULING.md b/documents/agents/adaptive-evaluation/PHASE_6_SCHEDULING.md similarity index 100% rename from ai-docs/adaptive-evaluation/PHASE_6_SCHEDULING.md rename to documents/agents/adaptive-evaluation/PHASE_6_SCHEDULING.md diff --git a/ai-docs/adaptive-evaluation/PHASE_7_TESTING.md b/documents/agents/adaptive-evaluation/PHASE_7_TESTING.md similarity index 100% rename from ai-docs/adaptive-evaluation/PHASE_7_TESTING.md rename to documents/agents/adaptive-evaluation/PHASE_7_TESTING.md diff --git a/ai-docs/adaptive-evaluation/README.md b/documents/agents/adaptive-evaluation/README.md similarity index 100% rename from ai-docs/adaptive-evaluation/README.md rename to documents/agents/adaptive-evaluation/README.md diff --git a/ai-docs/page-2026-02-09-10-30-47.md b/documents/agents/page-2026-02-09-10-30-47.md similarity index 100% rename from ai-docs/page-2026-02-09-10-30-47.md rename to documents/agents/page-2026-02-09-10-30-47.md diff --git a/mvnw b/mvnw deleted file mode 100755 index bd8896b..0000000 --- a/mvnw +++ /dev/null @@ -1,295 +0,0 @@ -#!/bin/sh -# ---------------------------------------------------------------------------- -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---------------------------------------------------------------------------- - -# ---------------------------------------------------------------------------- -# Apache Maven Wrapper startup batch script, version 3.3.4 -# -# Optional ENV vars -# ----------------- -# JAVA_HOME - location of a JDK home dir, required when download maven via java source -# MVNW_REPOURL - repo url base for downloading maven distribution -# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven -# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output -# ---------------------------------------------------------------------------- - -set -euf -[ "${MVNW_VERBOSE-}" != debug ] || set -x - -# OS specific support. -native_path() { printf %s\\n "$1"; } -case "$(uname)" in -CYGWIN* | MINGW*) - [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" - native_path() { cygpath --path --windows "$1"; } - ;; -esac - -# set JAVACMD and JAVACCMD -set_java_home() { - # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched - if [ -n "${JAVA_HOME-}" ]; then - if [ -x "$JAVA_HOME/jre/sh/java" ]; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - JAVACCMD="$JAVA_HOME/jre/sh/javac" - else - JAVACMD="$JAVA_HOME/bin/java" - JAVACCMD="$JAVA_HOME/bin/javac" - - if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then - echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 - echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 - return 1 - fi - fi - else - JAVACMD="$( - 'set' +e - 'unset' -f command 2>/dev/null - 'command' -v java - )" || : - JAVACCMD="$( - 'set' +e - 'unset' -f command 2>/dev/null - 'command' -v javac - )" || : - - if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then - echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 - return 1 - fi - fi -} - -# hash string like Java String::hashCode -hash_string() { - str="${1:-}" h=0 - while [ -n "$str" ]; do - char="${str%"${str#?}"}" - h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) - str="${str#?}" - done - printf %x\\n $h -} - -verbose() { :; } -[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } - -die() { - printf %s\\n "$1" >&2 - exit 1 -} - -trim() { - # MWRAPPER-139: - # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. - # Needed for removing poorly interpreted newline sequences when running in more - # exotic environments such as mingw bash on Windows. - printf "%s" "${1}" | tr -d '[:space:]' -} - -scriptDir="$(dirname "$0")" -scriptName="$(basename "$0")" - -# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties -while IFS="=" read -r key value; do - case "${key-}" in - distributionUrl) distributionUrl=$(trim "${value-}") ;; - distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; - esac -done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties" -[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" - -case "${distributionUrl##*/}" in -maven-mvnd-*bin.*) - MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ - case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in - *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; - :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; - :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; - :Linux*x86_64*) distributionPlatform=linux-amd64 ;; - *) - echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 - distributionPlatform=linux-amd64 - ;; - esac - distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" - ;; -maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; -*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; -esac - -# apply MVNW_REPOURL and calculate MAVEN_HOME -# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ -[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" -distributionUrlName="${distributionUrl##*/}" -distributionUrlNameMain="${distributionUrlName%.*}" -distributionUrlNameMain="${distributionUrlNameMain%-bin}" -MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" -MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" - -exec_maven() { - unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : - exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" -} - -if [ -d "$MAVEN_HOME" ]; then - verbose "found existing MAVEN_HOME at $MAVEN_HOME" - exec_maven "$@" -fi - -case "${distributionUrl-}" in -*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; -*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; -esac - -# prepare tmp dir -if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then - clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } - trap clean HUP INT TERM EXIT -else - die "cannot create temp dir" -fi - -mkdir -p -- "${MAVEN_HOME%/*}" - -# Download and Install Apache Maven -verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." -verbose "Downloading from: $distributionUrl" -verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" - -# select .zip or .tar.gz -if ! command -v unzip >/dev/null; then - distributionUrl="${distributionUrl%.zip}.tar.gz" - distributionUrlName="${distributionUrl##*/}" -fi - -# verbose opt -__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' -[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v - -# normalize http auth -case "${MVNW_PASSWORD:+has-password}" in -'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; -has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; -esac - -if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then - verbose "Found wget ... using wget" - wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" -elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then - verbose "Found curl ... using curl" - curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" -elif set_java_home; then - verbose "Falling back to use Java to download" - javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" - targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" - cat >"$javaSource" <<-END - public class Downloader extends java.net.Authenticator - { - protected java.net.PasswordAuthentication getPasswordAuthentication() - { - return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); - } - public static void main( String[] args ) throws Exception - { - setDefault( new Downloader() ); - java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); - } - } - END - # For Cygwin/MinGW, switch paths to Windows format before running javac and java - verbose " - Compiling Downloader.java ..." - "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" - verbose " - Running Downloader.java ..." - "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" -fi - -# If specified, validate the SHA-256 sum of the Maven distribution zip file -if [ -n "${distributionSha256Sum-}" ]; then - distributionSha256Result=false - if [ "$MVN_CMD" = mvnd.sh ]; then - echo "Checksum validation is not supported for maven-mvnd." >&2 - echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 - exit 1 - elif command -v sha256sum >/dev/null; then - if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then - distributionSha256Result=true - fi - elif command -v shasum >/dev/null; then - if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then - distributionSha256Result=true - fi - else - echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 - echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 - exit 1 - fi - if [ $distributionSha256Result = false ]; then - echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 - echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 - exit 1 - fi -fi - -# unzip and move -if command -v unzip >/dev/null; then - unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" -else - tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" -fi - -# Find the actual extracted directory name (handles snapshots where filename != directory name) -actualDistributionDir="" - -# First try the expected directory name (for regular distributions) -if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then - if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then - actualDistributionDir="$distributionUrlNameMain" - fi -fi - -# If not found, search for any directory with the Maven executable (for snapshots) -if [ -z "$actualDistributionDir" ]; then - # enable globbing to iterate over items - set +f - for dir in "$TMP_DOWNLOAD_DIR"/*; do - if [ -d "$dir" ]; then - if [ -f "$dir/bin/$MVN_CMD" ]; then - actualDistributionDir="$(basename "$dir")" - break - fi - fi - done - set -f -fi - -if [ -z "$actualDistributionDir" ]; then - verbose "Contents of $TMP_DOWNLOAD_DIR:" - verbose "$(ls -la "$TMP_DOWNLOAD_DIR")" - die "Could not find Maven distribution directory in extracted archive" -fi - -verbose "Found extracted Maven distribution directory: $actualDistributionDir" -printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url" -mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" - -clean || : -exec_maven "$@" diff --git a/mvnw.cmd b/mvnw.cmd deleted file mode 100644 index 92450f9..0000000 --- a/mvnw.cmd +++ /dev/null @@ -1,189 +0,0 @@ -<# : batch portion -@REM ---------------------------------------------------------------------------- -@REM Licensed to the Apache Software Foundation (ASF) under one -@REM or more contributor license agreements. See the NOTICE file -@REM distributed with this work for additional information -@REM regarding copyright ownership. The ASF licenses this file -@REM to you under the Apache License, Version 2.0 (the -@REM "License"); you may not use this file except in compliance -@REM with the License. You may obtain a copy of the License at -@REM -@REM http://www.apache.org/licenses/LICENSE-2.0 -@REM -@REM Unless required by applicable law or agreed to in writing, -@REM software distributed under the License is distributed on an -@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -@REM KIND, either express or implied. See the License for the -@REM specific language governing permissions and limitations -@REM under the License. -@REM ---------------------------------------------------------------------------- - -@REM ---------------------------------------------------------------------------- -@REM Apache Maven Wrapper startup batch script, version 3.3.4 -@REM -@REM Optional ENV vars -@REM MVNW_REPOURL - repo url base for downloading maven distribution -@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven -@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output -@REM ---------------------------------------------------------------------------- - -@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) -@SET __MVNW_CMD__= -@SET __MVNW_ERROR__= -@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% -@SET PSModulePath= -@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( - IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) -) -@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% -@SET __MVNW_PSMODULEP_SAVE= -@SET __MVNW_ARG0_NAME__= -@SET MVNW_USERNAME= -@SET MVNW_PASSWORD= -@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*) -@echo Cannot start maven from wrapper >&2 && exit /b 1 -@GOTO :EOF -: end batch / begin powershell #> - -$ErrorActionPreference = "Stop" -if ($env:MVNW_VERBOSE -eq "true") { - $VerbosePreference = "Continue" -} - -# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties -$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl -if (!$distributionUrl) { - Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" -} - -switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { - "maven-mvnd-*" { - $USE_MVND = $true - $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" - $MVN_CMD = "mvnd.cmd" - break - } - default { - $USE_MVND = $false - $MVN_CMD = $script -replace '^mvnw','mvn' - break - } -} - -# apply MVNW_REPOURL and calculate MAVEN_HOME -# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ -if ($env:MVNW_REPOURL) { - $MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" } - $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')" -} -$distributionUrlName = $distributionUrl -replace '^.*/','' -$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' - -$MAVEN_M2_PATH = "$HOME/.m2" -if ($env:MAVEN_USER_HOME) { - $MAVEN_M2_PATH = "$env:MAVEN_USER_HOME" -} - -if (-not (Test-Path -Path $MAVEN_M2_PATH)) { - New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null -} - -$MAVEN_WRAPPER_DISTS = $null -if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) { - $MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists" -} else { - $MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists" -} - -$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain" -$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' -$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" - -if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { - Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" - Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" - exit $? -} - -if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { - Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" -} - -# prepare tmp dir -$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile -$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" -$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null -trap { - if ($TMP_DOWNLOAD_DIR.Exists) { - try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } - catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } - } -} - -New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null - -# Download and Install Apache Maven -Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." -Write-Verbose "Downloading from: $distributionUrl" -Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" - -$webclient = New-Object System.Net.WebClient -if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { - $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) -} -[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null - -# If specified, validate the SHA-256 sum of the Maven distribution zip file -$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum -if ($distributionSha256Sum) { - if ($USE_MVND) { - Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." - } - Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash - if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { - Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." - } -} - -# unzip and move -Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null - -# Find the actual extracted directory name (handles snapshots where filename != directory name) -$actualDistributionDir = "" - -# First try the expected directory name (for regular distributions) -$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain" -$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD" -if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) { - $actualDistributionDir = $distributionUrlNameMain -} - -# If not found, search for any directory with the Maven executable (for snapshots) -if (!$actualDistributionDir) { - Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object { - $testPath = Join-Path $_.FullName "bin/$MVN_CMD" - if (Test-Path -Path $testPath -PathType Leaf) { - $actualDistributionDir = $_.Name - } - } -} - -if (!$actualDistributionDir) { - Write-Error "Could not find Maven distribution directory in extracted archive" -} - -Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir" -Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null -try { - Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null -} catch { - if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { - Write-Error "fail to move MAVEN_HOME" - } -} finally { - try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } - catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } -} - -Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/tests/requirements.txt b/tests/requirements.txt deleted file mode 100644 index 0d38274..0000000 --- a/tests/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -requests -redis -colorama diff --git a/tests/test_threshold_rules.py b/tests/test_threshold_rules.py deleted file mode 100644 index 2f680fc..0000000 --- a/tests/test_threshold_rules.py +++ /dev/null @@ -1,850 +0,0 @@ -#!/usr/bin/env python3 -""" -Production-Grade Threshold Rule Verification Tests -=================================================== -Comprehensive test suite for Eventara threshold rules with webhook notification verification. - -Tests ALL threshold rule types: -- Error metrics (ERROR_RATE, TOTAL_ERRORS) -- Latency metrics (AVG, P50, P95, P99, MAX, MIN) -- Throughput metrics (EVENTS_PER_MINUTE, EVENTS_LAST_X) -- Composite rules (AND, OR) -- Event Ratio rules -- Rate of Change rules (ERROR_RATE_CHANGE, LATENCY_CHANGE, THROUGHPUT_CHANGE) -- Webhook notification delivery -""" - -import requests -import redis -import time -import json -import random -import string -import sys -from datetime import datetime -from colorama import init, Fore, Style - -# Initialize colorama -init() - -# ============================================================================= -# CONFIGURATION -# ============================================================================= -API_URL = "http://localhost:8080" -REDIS_HOST = "localhost" -REDIS_PORT = 6379 -WEBHOOK_URL = "https://webhook.site/691fd6b2-20cf-4c4b-9eab-80896195ba6b" - -# Initialize Redis client -r = redis.Redis(host=REDIS_HOST, port=REDIS_PORT, decode_responses=True) - -# Track created resources for cleanup -created_rules = [] -created_channels = [] - -# Global notification channel for ALL tests -GLOBAL_CHANNEL_ID = None - -def flush_redis_test_data(): - """Clear Redis cooldown data to ensure clean test state.""" - print_step("Flushing Redis cooldown keys...") - try: - # Only delete cooldown keys - preserve metrics data - cooldown_keys = r.keys("eventara:rule:cooldown:*") - if cooldown_keys: - r.delete(*cooldown_keys) - print_info(f"Deleted {len(cooldown_keys)} cooldown keys") - else: - print_info("No cooldown keys to clean") - - print_success("Redis cooldowns cleared") - return True - except Exception as e: - print_fail(f"Failed to flush Redis: {e}") - return False - -# ============================================================================= -# UTILITY FUNCTIONS -# ============================================================================= - -def print_header(text): - print(f"\n{Fore.CYAN}{'='*70}") - print(f" {text}") - print(f"{'='*70}{Style.RESET_ALL}") - -def print_subheader(text): - print(f"\n{Fore.BLUE}--- {text} ---{Style.RESET_ALL}") - -def print_step(text): - print(f"{Fore.YELLOW}➜ {text}{Style.RESET_ALL}") - -def print_success(text): - print(f"{Fore.GREEN}✔ {text}{Style.RESET_ALL}") - -def print_fail(text): - print(f"{Fore.RED}✘ {text}{Style.RESET_ALL}") - -def print_info(text): - print(f"{Fore.MAGENTA}ℹ {text}{Style.RESET_ALL}") - -def generate_random_string(length=8): - return ''.join(random.choices(string.ascii_lowercase, k=length)) - -def generate_unique_name(prefix): - return f"{prefix}_{generate_random_string()}_{int(time.time())}" - -# ============================================================================= -# API HELPER FUNCTIONS -# ============================================================================= - -def create_notification_channel(name, webhook_url): - """Create a webhook notification channel.""" - url = f"{API_URL}/api/v1/notifications/channels" - payload = { - "channelType": "WEBHOOK", - "name": name, - "description": "Test webhook channel for threshold rule verification", - "enabled": True, - "config": { - "url": webhook_url, - "method": "POST", - "headers": { - "Content-Type": "application/json" - } - }, - "createdBy": "test-suite" - } - try: - response = requests.post(url, json=payload) - if response.status_code == 409: - print_info(f"Channel '{name}' already exists, fetching...") - # Get existing channel - channels = requests.get(url).json() - for ch in channels: - if ch.get("name") == name: - return ch - return None - response.raise_for_status() - channel = response.json() - created_channels.append(channel["id"]) - print_success(f"Created notification channel: {name} (ID: {channel['id']})") - return channel - except Exception as e: - print_fail(f"Failed to create notification channel: {e}") - return None - -def create_rule(rule_payload, notification_channel=None): - """Create a rule and link to global notification channel for verification.""" - global GLOBAL_CHANNEL_ID - url = f"{API_URL}/api/v1/rules" - - # Use global channel if available, or explicit channel if provided - channel_to_use = notification_channel or GLOBAL_CHANNEL_ID - if channel_to_use: - rule_payload["notificationChannels"] = [channel_to_use] - - try: - response = requests.post(url, json=rule_payload) - response.raise_for_status() - rule = response.json() - created_rules.append(rule["id"]) - - channel_info = f" → Webhook" if channel_to_use else "" - print_step(f"Created rule: {rule['name']} (ID: {rule['id']}){channel_info}") - - # Enable the rule immediately - enable_url = f"{API_URL}/api/v1/rules/{rule['id']}/enable" - requests.post(enable_url).raise_for_status() - print_step("Rule enabled") - - # Brief wait for rule cache invalidation to propagate - time.sleep(0.5) - - return rule - except Exception as e: - print_fail(f"Failed to create/enable rule: {e}") - if 'response' in locals(): - print(f"Response: {response.text}") - return None - -def delete_rule(rule_id): - """Delete a rule.""" - try: - url = f"{API_URL}/api/v1/rules/{rule_id}" - requests.delete(url) - except: - pass - -def delete_channel(channel_id): - """Delete a notification channel.""" - try: - url = f"{API_URL}/api/v1/notifications/channels/{channel_id}" - requests.delete(url) - except: - pass - -def ingest_event(event_payload): - """Ingest a single event.""" - url = f"{API_URL}/api/v1/events" - try: - response = requests.post(url, json=event_payload) - response.raise_for_status() - return True - except Exception as e: - print_fail(f"Failed to ingest event: {e}") - return False - -def ingest_events(events_list): - """Ingest multiple events.""" - success = 0 - for event in events_list: - if ingest_event(event): - success += 1 - return success == len(events_list) - -def check_redis_cooldown(rule_id, timeout=3.0): - """Check if Redis cooldown key exists within timeout.""" - key = f"eventara:rule:cooldown:{rule_id}" - start = time.time() - while time.time() - start < timeout: - if r.exists(key): - ttl = r.ttl(key) - print_success(f"Redis cooldown found for Rule {rule_id} (TTL: {ttl}s)") - return True - time.sleep(0.1) - print_fail(f"No Redis cooldown found for Rule {rule_id} within {timeout}s") - return False - -def check_no_redis_cooldown(rule_id): - """Verify Redis cooldown key does NOT exist.""" - key = f"eventara:rule:cooldown:{rule_id}" - time.sleep(0.5) # Brief wait for processing - if r.exists(key): - print_fail(f"Rule {rule_id} triggered unexpectedly!") - return False - print_success(f"Rule {rule_id} correctly did NOT trigger") - return True - -def get_alert_history(rule_id=None, limit=10): - """Fetch alert history from API.""" - url = f"{API_URL}/api/v1/alerts" - params = {"size": limit} - if rule_id: - params["ruleId"] = rule_id - try: - response = requests.get(url, params=params) - response.raise_for_status() - return response.json() - except: - return None - -def cleanup(): - """Clean up all created test resources.""" - print_subheader("Cleanup") - for rule_id in created_rules: - delete_rule(rule_id) - for channel_id in created_channels: - delete_channel(channel_id) - print_info(f"Cleaned up {len(created_rules)} rules and {len(created_channels)} channels") - -# ============================================================================= -# TEST CASES: ERROR METRICS -# ============================================================================= - -def test_error_rate_threshold(): - """Test ERROR_RATE metric with source filter.""" - print_header("TEST: ERROR_RATE Threshold (Source Specific)") - - source = generate_unique_name("error-svc") - rule = create_rule({ - "name": generate_unique_name("ErrorRateRule"), - "ruleType": "THRESHOLD", - "severity": "WARNING", - "ruleConfig": { - "metricType": "ERROR_RATE", - "condition": "GREATER_THAN", - "thresholdValue": 10.0, - "timeWindowMinutes": 5, - "cooldownMinutes": 2, - "sourceFilter": [source], - "minEventsToEvaluate": 1 - } - }) - if not rule: - return False - - # Send event from wrong source (should NOT trigger) - print_step("Testing wrong source (should NOT trigger)...") - ingest_event({"source": "other-service", "eventType": "error", "severity": "ERROR"}) - if not check_no_redis_cooldown(rule["id"]): - return False - - # Send event from correct source (should trigger) - print_step(f"Testing correct source: {source} (should trigger)...") - ingest_event({"source": source, "eventType": "error", "severity": "ERROR"}) - return check_redis_cooldown(rule["id"]) - -def test_total_errors_threshold(): - """Test TOTAL_ERRORS metric.""" - print_header("TEST: TOTAL_ERRORS Threshold") - - source = generate_unique_name("total-err-svc") - rule = create_rule({ - "name": generate_unique_name("TotalErrorsRule"), - "ruleType": "THRESHOLD", - "severity": "CRITICAL", - "ruleConfig": { - "metricType": "TOTAL_ERRORS", - "condition": "GREATER_THAN", - "thresholdValue": 0, # Trigger on any error - "timeWindowMinutes": 5, - "cooldownMinutes": 2, - "sourceFilter": [source] - } - }) - if not rule: - return False - - print_step("Sending error event...") - ingest_event({"source": source, "eventType": "system.failure", "severity": "ERROR"}) - return check_redis_cooldown(rule["id"]) - -# ============================================================================= -# TEST CASES: LATENCY METRICS -# ============================================================================= - -def test_avg_latency_threshold(): - """Test AVG_LATENCY metric.""" - print_header("TEST: AVG_LATENCY Threshold") - - source = generate_unique_name("latency-svc") - rule = create_rule({ - "name": generate_unique_name("AvgLatencyRule"), - "ruleType": "THRESHOLD", - "severity": "WARNING", - "ruleConfig": { - "metricType": "AVG_LATENCY", - "condition": "GREATER_THAN", - "thresholdValue": 500, - "timeWindowMinutes": 5, - "cooldownMinutes": 2, - "sourceFilter": [source] - } - }) - if not rule: - return False - - # Send low latency event (should NOT trigger) - print_step("Sending low latency event (100ms)...") - ingest_event({"source": source, "eventType": "api.request", "metadata": {"latency": 100}}) - if not check_no_redis_cooldown(rule["id"]): - return False - - # Send high latency event (should trigger) - print_step("Sending high latency event (1000ms)...") - ingest_event({"source": source, "eventType": "api.request", "metadata": {"latency": 1000}}) - return check_redis_cooldown(rule["id"]) - -def test_p95_latency_threshold(): - """Test P95_LATENCY metric (global percentile, ignores source filters).""" - print_header("TEST: P95_LATENCY Threshold") - - # Note: P95_LATENCY is computed globally, not per-source - rule = create_rule({ - "name": generate_unique_name("P95LatencyRule"), - "ruleType": "THRESHOLD", - "severity": "WARNING", - "ruleConfig": { - "metricType": "P95_LATENCY", - "condition": "GREATER_THAN", - "thresholdValue": 500, - "timeWindowMinutes": 5, - "cooldownMinutes": 2, - "minEventsToEvaluate": 5 - } - }) - if not rule: - return False - - # Send 20 events with most having high latency to ensure P95 is high - print_step("Sending 20 events with high latencies (P95 should exceed 500ms)...") - for i in range(20): - latency = 1000 + (i * 50) # 1000-1950ms - ingest_event({"source": "p95-test", "eventType": "api.request", "metadata": {"latency": latency}}) - - return check_redis_cooldown(rule["id"], timeout=3.0) - -# ============================================================================= -# TEST CASES: THROUGHPUT METRICS -# ============================================================================= - -def test_events_per_minute_threshold(): - """Test EVENTS_PER_MINUTE metric.""" - print_header("TEST: EVENTS_PER_MINUTE Threshold") - - source = generate_unique_name("throughput-svc") - rule = create_rule({ - "name": generate_unique_name("EventsPerMinRule"), - "ruleType": "THRESHOLD", - "severity": "INFO", - "ruleConfig": { - "metricType": "EVENTS_PER_MINUTE", - "condition": "GREATER_THAN", - "thresholdValue": 1, # Low threshold: 20 events / 5 min = 4 EPM > 1 - "timeWindowMinutes": 5, - "cooldownMinutes": 2, - "sourceFilter": [source], - "minEventsToEvaluate": 1 - } - }) - if not rule: - return False - - print_step("Sending burst of 20 events to achieve >1 EPM...") - for i in range(20): - ingest_event({"source": source, "eventType": "request"}) - - return check_redis_cooldown(rule["id"], timeout=4.0) - -def test_events_last_1_minute_threshold(): - """Test EVENTS_LAST_1_MINUTE metric.""" - print_header("TEST: EVENTS_LAST_1_MINUTE Threshold") - - source = generate_unique_name("window-svc") - rule = create_rule({ - "name": generate_unique_name("EventsLast1MinRule"), - "ruleType": "THRESHOLD", - "severity": "WARNING", - "ruleConfig": { - "metricType": "EVENTS_LAST_1_MINUTE", - "condition": "GREATER_THAN", - "thresholdValue": 3, - "cooldownMinutes": 2, - "minEventsToEvaluate": 1 - } - }) - if not rule: - return False - - print_step("Sending 5 events...") - for i in range(5): - ingest_event({"source": source, "eventType": "ping"}) - - return check_redis_cooldown(rule["id"], timeout=3.0) - -# ============================================================================= -# TEST CASES: COMPOSITE RULES -# ============================================================================= - -def test_composite_and_rule(): - """Test Composite rule with AND operator.""" - print_header("TEST: Composite Rule (AND)") - - source = generate_unique_name("composite-and-svc") - rule = create_rule({ - "name": generate_unique_name("CompositeANDRule"), - "ruleType": "THRESHOLD", - "severity": "CRITICAL", - "ruleConfig": { - "metricType": "TOTAL_EVENTS", - "thresholdValue": 0, - "condition": "GREATER_THAN", - "operator": "AND", - "sourceFilter": [source], - "conditions": [ - {"metricType": "ERROR_RATE", "condition": "GREATER_THAN", "value": 50.0}, - {"metricType": "AVG_LATENCY", "condition": "GREATER_THAN", "value": 500} - ], - "cooldownMinutes": 5, - "minEventsToEvaluate": 1 - } - }) - if not rule: - return False - - # Only error rate met (should NOT trigger) - print_step("Sending error with LOW latency (only 1 condition met)...") - ingest_event({"source": source, "eventType": "error", "severity": "ERROR", "metadata": {"latency": 100}}) - if not check_no_redis_cooldown(rule["id"]): - return False - - # Both conditions met (should trigger) - print_step("Sending error with HIGH latency (both conditions met)...") - ingest_event({"source": source, "eventType": "error", "severity": "ERROR", "metadata": {"latency": 1000}}) - return check_redis_cooldown(rule["id"]) - -def test_composite_or_rule(): - """Test Composite rule with OR operator.""" - print_header("TEST: Composite Rule (OR)") - - source = generate_unique_name("composite-or-svc") - rule = create_rule({ - "name": generate_unique_name("CompositeORRule"), - "ruleType": "THRESHOLD", - "severity": "WARNING", - "ruleConfig": { - "metricType": "TOTAL_EVENTS", - "thresholdValue": 0, - "condition": "GREATER_THAN", - "operator": "OR", - "sourceFilter": [source], - "conditions": [ - {"metricType": "ERROR_RATE", "condition": "GREATER_THAN", "value": 50.0}, - {"metricType": "AVG_LATENCY", "condition": "GREATER_THAN", "value": 5000} - ], - "cooldownMinutes": 5, - "minEventsToEvaluate": 1 - } - }) - if not rule: - return False - - # Only error rate met (should trigger with OR) - print_step("Sending error with LOW latency (only error condition met, OR should trigger)...") - ingest_event({"source": source, "eventType": "error", "severity": "ERROR", "metadata": {"latency": 100}}) - return check_redis_cooldown(rule["id"]) - -# ============================================================================= -# TEST CASES: EVENT RATIO RULES -# ============================================================================= - -def test_event_ratio_rule(): - """Test EVENT_RATIO metric (conversion rate).""" - print_header("TEST: EVENT_RATIO Rule (Conversion Rate)") - - numerator = generate_unique_name("checkout.success") - denominator = generate_unique_name("checkout.started") - - rule = create_rule({ - "name": generate_unique_name("ConversionRatioRule"), - "ruleType": "THRESHOLD", - "severity": "WARNING", - "ruleConfig": { - "metricType": "EVENT_RATIO", - "numeratorEventType": numerator, - "denominatorEventType": denominator, - "condition": "LESS_THAN", - "thresholdValue": 0.8, - "minDenominatorEvents": 2, - "cooldownMinutes": 5 - } - }) - if not rule: - return False - - # 100% ratio (1/1) - should NOT trigger - print_step("Sending 1 success / 1 attempt (ratio=1.0, should NOT trigger)...") - ingest_event({"eventType": denominator, "source": "checkout"}) - ingest_event({"eventType": numerator, "source": "checkout"}) - if not check_no_redis_cooldown(rule["id"]): - return False - - # 50% ratio (1/2) - should trigger - print_step("Adding 1 more attempt (ratio=0.5, should trigger)...") - ingest_event({"eventType": denominator, "source": "checkout"}) - return check_redis_cooldown(rule["id"]) - -# ============================================================================= -# TEST CASES: RATE OF CHANGE RULES -# ============================================================================= - -def test_error_rate_change(): - """Test ERROR_RATE_CHANGE metric.""" - print_header("TEST: ERROR_RATE_CHANGE (Spike Detection)") - - source = generate_unique_name("spike-svc") - rule = create_rule({ - "name": generate_unique_name("ErrorRateChangeRule"), - "ruleType": "THRESHOLD", - "severity": "CRITICAL", - "ruleConfig": { - "metricType": "ERROR_RATE_CHANGE", - "condition": "GREATER_THAN", - "thresholdValue": 50.0, - "timeWindowMinutes": 1, - "sourceFilter": [source] - } - }) - if not rule: - return False - - print_step("Sending error events (rate change detection)...") - for _ in range(3): - ingest_event({"source": source, "eventType": "error", "severity": "ERROR"}) - - # Rate of change rules require window comparison - just verify no crash - print_success("ERROR_RATE_CHANGE rule created and processed without error") - return True - -def test_latency_change(): - """Test LATENCY_CHANGE metric.""" - print_header("TEST: LATENCY_CHANGE") - - source = generate_unique_name("latency-change-svc") - rule = create_rule({ - "name": generate_unique_name("LatencyChangeRule"), - "ruleType": "THRESHOLD", - "severity": "WARNING", - "ruleConfig": { - "metricType": "LATENCY_CHANGE", - "condition": "GREATER_THAN", - "thresholdValue": 100.0, - "timeWindowMinutes": 1, - "sourceFilter": [source] - } - }) - if not rule: - return False - - print_step("Sending high latency events...") - for latency in [1000, 2000, 3000]: - ingest_event({"source": source, "eventType": "request", "metadata": {"latency": latency}}) - - print_success("LATENCY_CHANGE rule created and processed without error") - return True - -def test_throughput_change(): - """Test THROUGHPUT_CHANGE metric.""" - print_header("TEST: THROUGHPUT_CHANGE") - - source = generate_unique_name("throughput-change-svc") - rule = create_rule({ - "name": generate_unique_name("ThroughputChangeRule"), - "ruleType": "THRESHOLD", - "severity": "INFO", - "ruleConfig": { - "metricType": "THROUGHPUT_CHANGE", - "condition": "GREATER_THAN", - "thresholdValue": 50.0, - "timeWindowMinutes": 1, - "sourceFilter": [source] - } - }) - if not rule: - return False - - print_step("Sending burst of events...") - for _ in range(10): - ingest_event({"source": source, "eventType": "request"}) - - print_success("THROUGHPUT_CHANGE rule created and processed without error") - return True - -# ============================================================================= -# TEST CASES: NOTIFICATION VERIFICATION -# ============================================================================= - -def test_webhook_notification(): - """Test that webhook notifications are sent when rules trigger.""" - print_header("TEST: Webhook Notification Delivery") - - # Create notification channel - channel_name = generate_unique_name("test-webhook") - channel = create_notification_channel(channel_name, WEBHOOK_URL) - if not channel: - print_fail("Could not create notification channel") - return False - - source = generate_unique_name("notif-svc") - rule = create_rule({ - "name": generate_unique_name("WebhookNotifRule"), - "ruleType": "THRESHOLD", - "severity": "CRITICAL", - "ruleConfig": { - "metricType": "ERROR_RATE", - "condition": "GREATER_THAN", - "thresholdValue": 10.0, - "timeWindowMinutes": 5, - "cooldownMinutes": 2, - "sourceFilter": [source], - "minEventsToEvaluate": 1 - } - }, notification_channel=channel_name) - - if not rule: - return False - - print_step("Sending triggering event...") - ingest_event({"source": source, "eventType": "error", "severity": "ERROR"}) - - # Check cooldown (rule triggered) - allow more time for newly created channel - if not check_redis_cooldown(rule["id"], timeout=5.0): - return False - - # Wait for async notification - print_step("Waiting for webhook notification (check webhook.site)...") - time.sleep(2) - - print_info(f"Check webhook.site for notification: {WEBHOOK_URL}") - print_success("Webhook notification test completed - verify at webhook.site") - return True - -def test_alert_history_created(): - """Test that alert history records are created when rules trigger.""" - print_header("TEST: Alert History Creation") - - source = generate_unique_name("alert-history-svc") - rule = create_rule({ - "name": generate_unique_name("AlertHistoryRule"), - "ruleType": "THRESHOLD", - "severity": "WARNING", - "ruleConfig": { - "metricType": "ERROR_RATE", - "condition": "GREATER_THAN", - "thresholdValue": 10.0, - "timeWindowMinutes": 5, - "cooldownMinutes": 2, - "sourceFilter": [source], - "minEventsToEvaluate": 1 - } - }) - if not rule: - return False - - print_step("Triggering alert...") - ingest_event({"source": source, "eventType": "error", "severity": "ERROR"}) - - if not check_redis_cooldown(rule["id"], timeout=5.0): - return False - - # Check alert history API - print_step("Checking alert history API...") - time.sleep(1) - - try: - response = requests.get(f"{API_URL}/api/v1/alerts", params={"size": 20}) - if response.status_code == 200: - alerts = response.json() - # Check if our rule's alert exists - content = alerts.get("content", alerts) if isinstance(alerts, dict) else alerts - for alert in content if isinstance(content, list) else []: - if alert.get("ruleId") == rule["id"] or alert.get("ruleName") == rule["name"]: - print_success(f"Alert history record found for rule {rule['id']}") - return True - print_info("Alert may be in history - check API response") - return True - else: - print_info(f"Alert history API returned {response.status_code}") - return True # Don't fail if API format differs - except Exception as e: - print_info(f"Could not verify alert history: {e}") - return True # Test passes if rule triggered - -# ============================================================================= -# MAIN TEST RUNNER -# ============================================================================= - -def run_all_tests(): - """Run all tests and return results.""" - global GLOBAL_CHANNEL_ID - - print(f"\n{Fore.CYAN}{'='*70}") - print(" PRODUCTION-GRADE THRESHOLD RULE VERIFICATION") - print(f" Started: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") - print(f"{'='*70}{Style.RESET_ALL}\n") - - # Step 1: Flush Redis data to ensure clean state - flush_redis_test_data() - - # Step 2: Create global notification channel for ALL tests - print_step("Creating global notification channel for all tests...") - channel = create_notification_channel( - generate_unique_name("global-webhook"), - WEBHOOK_URL - ) - if channel: - GLOBAL_CHANNEL_ID = channel["id"] - print_success(f"All rules will send notifications to webhook (Channel ID: {GLOBAL_CHANNEL_ID})") - else: - print_fail("Failed to create global notification channel - notifications won't be verified") - - time.sleep(1) # Brief wait for channel to be ready - - tests = [ - # Error Metrics - ("ERROR_RATE Threshold", test_error_rate_threshold), - ("TOTAL_ERRORS Threshold", test_total_errors_threshold), - - # Latency Metrics - ("AVG_LATENCY Threshold", test_avg_latency_threshold), - ("P95_LATENCY Threshold", test_p95_latency_threshold), - - # Throughput Metrics - ("EVENTS_PER_MINUTE Threshold", test_events_per_minute_threshold), - ("EVENTS_LAST_1_MINUTE Threshold", test_events_last_1_minute_threshold), - - # Composite Rules - ("Composite AND Rule", test_composite_and_rule), - ("Composite OR Rule", test_composite_or_rule), - - # Ratio Rules - ("EVENT_RATIO Rule", test_event_ratio_rule), - - # Rate of Change Rules - ("ERROR_RATE_CHANGE", test_error_rate_change), - ("LATENCY_CHANGE", test_latency_change), - ("THROUGHPUT_CHANGE", test_throughput_change), - - # Notification Tests - ("Webhook Notification", test_webhook_notification), - ("Alert History Creation", test_alert_history_created), - ] - - results = [] - for name, test_fn in tests: - try: - result = test_fn() - results.append((name, result)) - except Exception as e: - print_fail(f"Test '{name}' crashed: {e}") - results.append((name, False)) - - return results - -def print_summary(results): - """Print test summary.""" - print_header("TEST SUMMARY") - - passed = sum(1 for _, r in results if r) - total = len(results) - - for name, result in results: - status = f"{Fore.GREEN}PASS{Style.RESET_ALL}" if result else f"{Fore.RED}FAIL{Style.RESET_ALL}" - print(f" {status} {name}") - - print() - if passed == total: - print(f"{Fore.GREEN}{'='*70}") - print(f" ALL {total} TESTS PASSED! ✓") - print(f"{'='*70}{Style.RESET_ALL}") - else: - print(f"{Fore.RED}{'='*70}") - print(f" {passed}/{total} TESTS PASSED") - print(f"{'='*70}{Style.RESET_ALL}") - - print(f"\n{Fore.MAGENTA}ℹ Webhook notifications sent to: {WEBHOOK_URL}{Style.RESET_ALL}") - print(f"{Fore.MAGENTA}ℹ Check webhook.site to verify notification delivery{Style.RESET_ALL}\n") - -def main(): - try: - results = run_all_tests() - print_summary(results) - - # Cleanup - cleanup() - - # Exit with appropriate code - all_passed = all(r for _, r in results) - sys.exit(0 if all_passed else 1) - - except KeyboardInterrupt: - print("\n\nTest interrupted by user") - cleanup() - sys.exit(1) - except Exception as e: - print_fail(f"Test suite error: {e}") - cleanup() - sys.exit(1) - -if __name__ == "__main__": - main() diff --git a/tests/yc-demo-events.sh b/tests/yc-demo-events.sh index 672a6e1..fadf72e 100755 --- a/tests/yc-demo-events.sh +++ b/tests/yc-demo-events.sh @@ -8,7 +8,7 @@ # Rule 3: Checkout Conversion Drop - checkout.success/checkout.started < 0.7 (min 5 events) # Rule 4: Error Rate Anomaly - ERROR_RATE_CHANGE > 100% -API_URL="${API_URL:-http://localhost:8080/api/v1/events}" +API_URL="${API_URL:-http://localhost/api/v1/events}" echo "" echo "EVENTARA YC DEMO - Triggering All 4 Rules"