ElixirScope (elixir_scope v0.0.1)

ElixirScope - AI-Powered Execution Cinema Debugger

ElixirScope provides deep observability and "execution cinema" capabilities for Elixir applications. It enables time-travel debugging, comprehensive event capture, and AI-powered analysis of concurrent systems.

Features

  • Total Recall: Capture complete execution history with minimal overhead
  • AI-Driven Instrumentation: Intelligent, automatic code instrumentation
  • Execution Cinema: Visual time-travel debugging interface
  • Multi-Dimensional Analysis: Correlate events across time, processes, state, and causality
  • Performance Aware: <1% overhead in production with smart sampling

Quick Start

# Start ElixirScope with default configuration
ElixirScope.start()

# Configure for development with full tracing
ElixirScope.start(strategy: :full_trace)

# Query captured events
events = ElixirScope.get_events(pid: self(), limit: 100)

# Stop tracing
ElixirScope.stop()

Configuration

ElixirScope can be configured via config.exs:

config :elixir_scope,
  ai: [
    planning: [
      default_strategy: :balanced,
      performance_target: 0.01,
      sampling_rate: 1.0
    ]
  ],
  capture: [
    ring_buffer: [
      size: 1_048_576,
      max_events: 100_000
    ]
  ]

See ElixirScope.Config for all available configuration options.

Summary

Functions

Manually triggers AI analysis of the current codebase.

Gets the current configuration.

Queries captured events based on the given criteria.

Gets message flow between two processes.

Reconstructs the state of a GenServer at a specific timestamp.

Gets the state history for a GenServer process.

Checks if ElixirScope is currently running.

Starts ElixirScope with the given options.

Gets the current status of ElixirScope.

Stops ElixirScope and all tracing.

Updates configuration at runtime.

Updates the instrumentation plan at runtime.

Types

event_query()

@type event_query() :: [
  pid: pid() | :all,
  event_type: atom() | :all,
  since: integer() | DateTime.t(),
  until: integer() | DateTime.t(),
  limit: pos_integer()
]

start_option()

@type start_option() ::
  {:strategy, :minimal | :balanced | :full_trace}
  | {:sampling_rate, float()}
  | {:modules, [module()]}
  | {:exclude_modules, [module()]}

Functions

analyze_codebase(opts \\ [])

@spec analyze_codebase(keyword()) :: :ok | {:error, term()}

Manually triggers AI analysis of the current codebase.

This can be useful to refresh instrumentation plans after code changes or to analyze new modules.

Examples

# Analyze entire codebase
ElixirScope.analyze_codebase()

# Analyze specific modules
ElixirScope.analyze_codebase(modules: [MyApp.NewModule])

get_config()

@spec get_config() :: ElixirScope.Config.t() | {:error, term()}

Gets the current configuration.

Examples

config = ElixirScope.get_config()
sampling_rate = config.ai.planning.sampling_rate

get_events(query \\ [])

@spec get_events(event_query()) :: [ElixirScope.Events.t()] | {:error, term()}

Queries captured events based on the given criteria.

Query Options

  • :pid - Filter by process ID (:all for all processes)
  • :event_type - Filter by event type (:all for all types)
  • :since - Events since timestamp or DateTime
  • :until - Events until timestamp or DateTime
  • :limit - Maximum number of events to return

Examples

# Get last 100 events for current process
events = ElixirScope.get_events(pid: self(), limit: 100)

# Get all function entry events
events = ElixirScope.get_events(event_type: :function_entry)

# Get events from the last minute
since = DateTime.utc_now() |> DateTime.add(-60, :second)
events = ElixirScope.get_events(since: since)

get_message_flow(sender_pid, receiver_pid, opts \\ [])

@spec get_message_flow(pid(), pid(), keyword()) ::
  [ElixirScope.Events.MessageSend.t()] | {:error, term()}

Gets message flow between two processes.

Returns all messages sent between the specified processes within the given time range.

Examples

# Get all messages between two processes
messages = ElixirScope.get_message_flow(sender_pid, receiver_pid)

# Get messages in a time range
messages = ElixirScope.get_message_flow(
  sender_pid, 
  receiver_pid, 
  since: start_time, 
  until: end_time
)

get_state_at(pid, timestamp)

@spec get_state_at(pid(), integer()) :: term() | {:error, term()}

Reconstructs the state of a GenServer at a specific timestamp.

Examples

timestamp = ElixirScope.Utils.monotonic_timestamp()
state = ElixirScope.get_state_at(pid, timestamp)

get_state_history(pid)

@spec get_state_history(pid()) ::
  [ElixirScope.Events.StateChange.t()] | {:error, term()}

Gets the state history for a GenServer process.

Returns a chronological list of state changes for the given process.

Examples

# Get state history for a GenServer
history = ElixirScope.get_state_history(pid)

# Get state at a specific time
state = ElixirScope.get_state_at(pid, timestamp)

running?()

@spec running?() :: boolean()

Checks if ElixirScope is currently running.

Examples

if ElixirScope.running?() do
  # ElixirScope is active
end

start(opts \\ [])

@spec start([start_option()]) :: :ok | {:error, term()}

Starts ElixirScope with the given options.

Options

  • :strategy - Instrumentation strategy (:minimal, :balanced, :full_trace)
  • :sampling_rate - Event sampling rate (0.0 to 1.0)
  • :modules - Specific modules to instrument (overrides AI planning)
  • :exclude_modules - Modules to exclude from instrumentation

Examples

# Start with default configuration
ElixirScope.start()

# Start with full tracing for debugging
ElixirScope.start(strategy: :full_trace, sampling_rate: 1.0)

# Start with minimal overhead for production
ElixirScope.start(strategy: :minimal, sampling_rate: 0.1)

# Instrument only specific modules
ElixirScope.start(modules: [MyApp.Worker, MyApp.Server])

status()

@spec status() :: map()

Gets the current status of ElixirScope.

Returns a map with information about:

  • Whether ElixirScope is running
  • Current configuration
  • Performance statistics
  • Storage usage

Examples

status = ElixirScope.status()
# %{
#   running: true,
#   config: %{...},
#   stats: %{events_captured: 12345, ...},
#   storage: %{hot_events: 5000, memory_usage: "2.1 MB"}
# }

stop()

@spec stop() :: :ok

Stops ElixirScope and all tracing.

Examples

ElixirScope.stop()

update_config(path, value)

@spec update_config([atom()], term()) :: :ok | {:error, term()}

Updates configuration at runtime.

Only certain configuration paths can be updated at runtime for safety.

Examples

# Update sampling rate
ElixirScope.update_config([:ai, :planning, :sampling_rate], 0.8)

# Update query timeout
ElixirScope.update_config([:interface, :query_timeout], 10_000)

update_instrumentation(updates)

@spec update_instrumentation(keyword()) :: :ok | {:error, term()}

Updates the instrumentation plan at runtime.

This allows changing which modules and functions are being traced without restarting the application.

Examples

# Change sampling rate
ElixirScope.update_instrumentation(sampling_rate: 0.5)

# Add modules to trace
ElixirScope.update_instrumentation(add_modules: [MyApp.NewModule])

# Change strategy  
ElixirScope.update_instrumentation(strategy: :full_trace)