diff --git a/Dockerfile b/Dockerfile index 9efb6a9..6258d72 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,7 +27,7 @@ RUN apt-get update -qq && \ *) \ echo "Unsupported platform: $TARGETPLATFORM" && exit 1 ;; \ esac && \ - wget "https://github.com/duckdb/duckdb/releases/download/v1.4.2/libduckdb-linux-${DUCKDB_ARCH}.zip" -O /tmp/libduckdb.zip && \ + wget "https://github.com/duckdb/duckdb/releases/download/v1.4.3/libduckdb-linux-${DUCKDB_ARCH}.zip" -O /tmp/libduckdb.zip && \ unzip /tmp/libduckdb.zip -d /tmp/duckdb && \ cp /tmp/duckdb/duckdb.h /tmp/duckdb/duckdb.hpp /usr/local/include/ && \ cp /tmp/duckdb/libduckdb.so /usr/local/lib/ && \ diff --git a/app/controllers/analytics_controller.rb b/app/controllers/analytics_controller.rb index aa5f98b..3953293 100644 --- a/app/controllers/analytics_controller.rb +++ b/app/controllers/analytics_controller.rb @@ -28,56 +28,40 @@ class AnalyticsController < ApplicationController # Core statistics - cached (uses DuckDB if available) stat_start = Time.current - @total_events = Rails.cache.fetch("#{cache_key_base}/total_events", expires_in: cache_ttl) do - with_duckdb_fallback { EventDdb.count_since(@start_time) } || - Event.where("timestamp >= ?", @start_time).count - end + @total_events = BaffleDl.count_since(@start_time) Rails.logger.info "[Analytics Perf] Total events: #{((Time.current - stat_start) * 1000).round(1)}ms" @total_rules = Rails.cache.fetch("analytics/total_rules", expires_in: 5.minutes) do Rule.enabled.count end - @network_ranges_with_events = Rails.cache.fetch("analytics/network_ranges_with_events", expires_in: 5.minutes) do - NetworkRange.with_events.count - end + @network_ranges_with_events = BaffleDl.count_network_ranges_with_events(@start_time) || + Rails.cache.fetch("analytics/network_ranges_with_events", expires_in: 5.minutes) do + NetworkRange.with_events.count + end @total_network_ranges = Rails.cache.fetch("analytics/total_network_ranges", expires_in: 5.minutes) do NetworkRange.count end - # Event breakdown by action - cached (uses DuckDB if available) + # Event breakdown by action - use DuckDB directly for performance stat_start = Time.current @event_breakdown = Rails.cache.fetch("#{cache_key_base}/event_breakdown", expires_in: cache_ttl) do - with_duckdb_fallback { EventDdb.breakdown_by_action(@start_time) } || - Event.where("timestamp >= ?", @start_time) - .group(:waf_action) - .count + BaffleDl.breakdown_by_action(@start_time) || {} end Rails.logger.info "[Analytics Perf] Event breakdown: #{((Time.current - stat_start) * 1000).round(1)}ms" - # Top countries by event count - cached (uses DuckDB if available) + # Top countries by event count - use DuckDB directly for performance stat_start = Time.current @top_countries = Rails.cache.fetch("#{cache_key_base}/top_countries", expires_in: cache_ttl) do - with_duckdb_fallback { EventDdb.top_countries(@start_time, 10) } || - Event.where("timestamp >= ? AND country IS NOT NULL", @start_time) - .group(:country) - .count - .sort_by { |_, count| -count } - .first(10) + BaffleDl.top_countries(@start_time, 10) || [] end Rails.logger.info "[Analytics Perf] Top countries: #{((Time.current - stat_start) * 1000).round(1)}ms" - # Top blocked IPs - cached (uses DuckDB if available) + # Top blocked IPs - use DuckDB directly for performance stat_start = Time.current @top_blocked_ips = Rails.cache.fetch("#{cache_key_base}/top_blocked_ips", expires_in: cache_ttl) do - with_duckdb_fallback { EventDdb.top_blocked_ips(@start_time, 10) } || - Event.where("timestamp >= ?", @start_time) - .where(waf_action: 0) # deny action in enum - .group(:ip_address) - .count - .sort_by { |_, count| -count } - .first(10) + BaffleDl.top_blocked_ips(@start_time, 10) || [] end Rails.logger.info "[Analytics Perf] Top blocked IPs: #{((Time.current - stat_start) * 1000).round(1)}ms" @@ -135,8 +119,8 @@ class AnalyticsController < ApplicationController @time_period = params[:period]&.to_sym || :day @start_time = calculate_start_time(@time_period) - # Top networks by request volume - use DuckDB if available - network_stats = with_duckdb_fallback { EventDdb.top_networks(@start_time, 50) } + # Top networks by request volume - use DuckLake if available + network_stats = with_duckdb_fallback { BaffleDl.top_networks(@start_time, 50) } if network_stats # DuckDB path: array format [network_range_id, event_count, unique_ips] @@ -200,24 +184,24 @@ class AnalyticsController < ApplicationController # Network type breakdown with traffic stats @network_breakdown = calculate_network_type_stats(@start_time) - # Company breakdown for top traffic sources - use DuckDB if available - @top_companies = with_duckdb_fallback { EventDdb.top_companies(@start_time, 20) } || + # Company breakdown for top traffic sources - use DuckLake if available + @top_companies = with_duckdb_fallback { BaffleDl.top_companies(@start_time, 20) } || Event.where("timestamp >= ? AND company IS NOT NULL", @start_time) .group(:company) .select("company, COUNT(*) as event_count, COUNT(DISTINCT ip_address) as unique_ips, COUNT(DISTINCT network_range_id) as network_count") .order("event_count DESC") .limit(20) - # ASN breakdown - use DuckDB if available - @top_asns = with_duckdb_fallback { EventDdb.top_asns(@start_time, 15) } || + # ASN breakdown - use DuckLake if available + @top_asns = with_duckdb_fallback { BaffleDl.top_asns(@start_time, 15) } || Event.where("timestamp >= ? AND asn IS NOT NULL", @start_time) .group(:asn, :asn_org) .select("asn, asn_org, COUNT(*) as event_count, COUNT(DISTINCT ip_address) as unique_ips, COUNT(DISTINCT network_range_id) as network_count") .order("event_count DESC") .limit(15) - # Geographic breakdown - use DuckDB if available - @top_countries = with_duckdb_fallback { EventDdb.top_countries_with_stats(@start_time, 15) } || + # Geographic breakdown - use DuckLake if available + @top_countries = with_duckdb_fallback { BaffleDl.top_countries_with_stats(@start_time, 15) } || Event.where("timestamp >= ? AND country IS NOT NULL", @start_time) .group(:country) .select("country, COUNT(*) as event_count, COUNT(DISTINCT ip_address) as unique_ips")