Skip to main content

trustformers_debug/
interactive_debugger.rs

1//! Interactive Debugger for TrustformeRS
2//!
3//! Provides step-through execution, breakpoints, variable inspection,
4//! call stack visualization, and time-travel debugging capabilities.
5
6use anyhow::Result;
7use chrono::{DateTime, Utc};
8use indexmap::IndexMap;
9use parking_lot::{Mutex, RwLock};
10use serde::{Deserialize, Serialize};
11use std::collections::{HashMap, VecDeque};
12use std::sync::Arc;
13use uuid::Uuid;
14
15use crate::DebugConfig;
16
17/// Interactive debugger for step-through debugging and inspection
18#[derive(Debug)]
19pub struct InteractiveDebugger {
20    #[allow(dead_code)]
21    config: DebugConfig,
22    state: Arc<RwLock<DebuggerState>>,
23    breakpoints: Arc<RwLock<HashMap<String, Breakpoint>>>,
24    execution_history: Arc<Mutex<VecDeque<ExecutionSnapshot>>>,
25    current_step: Arc<Mutex<usize>>,
26    max_history_size: usize,
27}
28
29/// Current state of the debugger
30#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct DebuggerState {
32    pub is_running: bool,
33    pub is_paused: bool,
34    pub current_location: Option<DebugLocation>,
35    pub call_stack: Vec<StackFrame>,
36    pub variables: IndexMap<String, VariableValue>,
37    pub step_mode: StepMode,
38    pub session_start: DateTime<Utc>,
39}
40
41/// Debugging location identifier
42#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct DebugLocation {
44    pub module: String,
45    pub function: String,
46    pub line: Option<u32>,
47    pub instruction: Option<String>,
48    pub context: Option<String>,
49}
50
51/// Call stack frame
52#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct StackFrame {
54    pub id: Uuid,
55    pub location: DebugLocation,
56    pub locals: IndexMap<String, VariableValue>,
57    pub timestamp: DateTime<Utc>,
58    pub depth: usize,
59}
60
61/// Variable value with type information
62#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct VariableValue {
64    pub name: String,
65    pub value: String,
66    pub type_name: String,
67    pub size_bytes: Option<usize>,
68    pub shape: Option<Vec<usize>>,
69    pub is_tensor: bool,
70    pub metadata: HashMap<String, String>,
71}
72
73/// Execution step mode
74#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
75pub enum StepMode {
76    /// Step into function calls
77    StepInto,
78    /// Step over function calls
79    StepOver,
80    /// Step out of current function
81    StepOut,
82    /// Continue until next breakpoint
83    Continue,
84    /// Single instruction step
85    SingleStep,
86}
87
88/// Breakpoint configuration
89#[derive(Debug, Clone, Serialize, Deserialize)]
90pub struct Breakpoint {
91    pub id: Uuid,
92    pub location: DebugLocation,
93    pub condition: Option<String>,
94    pub hit_count: usize,
95    pub enabled: bool,
96    pub temporary: bool,
97    pub log_message: Option<String>,
98    pub created_at: DateTime<Utc>,
99}
100
101/// Snapshot of execution state for time-travel debugging
102#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct ExecutionSnapshot {
104    pub id: Uuid,
105    pub timestamp: DateTime<Utc>,
106    pub step_number: usize,
107    pub location: DebugLocation,
108    pub call_stack: Vec<StackFrame>,
109    pub variables: IndexMap<String, VariableValue>,
110    pub memory_usage: Option<usize>,
111    pub performance_metrics: HashMap<String, f64>,
112}
113
114/// Debugger command for external control
115#[derive(Debug, Clone, Serialize, Deserialize)]
116pub enum DebuggerCommand {
117    Start,
118    Pause,
119    Resume,
120    Step(StepMode),
121    SetBreakpoint(DebugLocation, Option<String>),
122    RemoveBreakpoint(Uuid),
123    InspectVariable(String),
124    EvaluateExpression(String),
125    ShowCallStack,
126    ShowHistory,
127    JumpToStep(usize),
128    Reset,
129    Exit,
130}
131
132/// Response from debugger operations
133#[derive(Debug, Clone, Serialize, Deserialize)]
134pub enum DebuggerResponse {
135    Started,
136    Paused(DebugLocation),
137    Resumed,
138    Stepped(ExecutionSnapshot),
139    BreakpointHit(Breakpoint, ExecutionSnapshot),
140    VariableInspected(VariableValue),
141    ExpressionEvaluated(String),
142    CallStackShown(Vec<StackFrame>),
143    HistoryShown(Vec<ExecutionSnapshot>),
144    JumpedToStep(ExecutionSnapshot),
145    Reset,
146    Error(String),
147}
148
149impl InteractiveDebugger {
150    /// Create a new interactive debugger
151    pub fn new(config: &DebugConfig) -> Self {
152        Self {
153            config: config.clone(),
154            state: Arc::new(RwLock::new(DebuggerState {
155                is_running: false,
156                is_paused: false,
157                current_location: None,
158                call_stack: Vec::new(),
159                variables: IndexMap::new(),
160                step_mode: StepMode::Continue,
161                session_start: Utc::now(),
162            })),
163            breakpoints: Arc::new(RwLock::new(HashMap::new())),
164            execution_history: Arc::new(Mutex::new(VecDeque::new())),
165            current_step: Arc::new(Mutex::new(0)),
166            max_history_size: config.max_gradient_history, // Reuse config value
167        }
168    }
169
170    /// Start the debugger
171    pub async fn start(&mut self) -> Result<()> {
172        let mut state = self.state.write();
173        state.is_running = true;
174        state.session_start = Utc::now();
175        tracing::info!("Interactive debugger started");
176        Ok(())
177    }
178
179    /// Process a debugger command
180    pub async fn process_command(&self, command: DebuggerCommand) -> Result<DebuggerResponse> {
181        match command {
182            DebuggerCommand::Start => {
183                let mut state = self.state.write();
184                state.is_running = true;
185                Ok(DebuggerResponse::Started)
186            },
187
188            DebuggerCommand::Pause => {
189                let mut state = self.state.write();
190                state.is_paused = true;
191                if let Some(location) = &state.current_location {
192                    Ok(DebuggerResponse::Paused(location.clone()))
193                } else {
194                    Ok(DebuggerResponse::Paused(DebugLocation {
195                        module: "unknown".to_string(),
196                        function: "unknown".to_string(),
197                        line: None,
198                        instruction: None,
199                        context: None,
200                    }))
201                }
202            },
203
204            DebuggerCommand::Resume => {
205                let mut state = self.state.write();
206                state.is_paused = false;
207                state.step_mode = StepMode::Continue;
208                Ok(DebuggerResponse::Resumed)
209            },
210
211            DebuggerCommand::Step(mode) => self.execute_step(mode).await,
212
213            DebuggerCommand::SetBreakpoint(location, condition) => {
214                self.set_breakpoint(location, condition).await
215            },
216
217            DebuggerCommand::RemoveBreakpoint(id) => self.remove_breakpoint(id).await,
218
219            DebuggerCommand::InspectVariable(name) => self.inspect_variable(&name).await,
220
221            DebuggerCommand::EvaluateExpression(expr) => self.evaluate_expression(&expr).await,
222
223            DebuggerCommand::ShowCallStack => {
224                let state = self.state.read();
225                Ok(DebuggerResponse::CallStackShown(state.call_stack.clone()))
226            },
227
228            DebuggerCommand::ShowHistory => {
229                let history = self.execution_history.lock();
230                Ok(DebuggerResponse::HistoryShown(
231                    history.iter().cloned().collect(),
232                ))
233            },
234
235            DebuggerCommand::JumpToStep(step_num) => self.jump_to_step(step_num).await,
236
237            DebuggerCommand::Reset => self.reset().await,
238
239            DebuggerCommand::Exit => {
240                let mut state = self.state.write();
241                state.is_running = false;
242                Ok(DebuggerResponse::Reset)
243            },
244        }
245    }
246
247    /// Execute a debugging step
248    async fn execute_step(&self, mode: StepMode) -> Result<DebuggerResponse> {
249        let mut state = self.state.write();
250        state.step_mode = mode;
251        state.is_paused = true;
252
253        // Create execution snapshot
254        let snapshot = ExecutionSnapshot {
255            id: Uuid::new_v4(),
256            timestamp: Utc::now(),
257            step_number: {
258                let mut step = self.current_step.lock();
259                *step += 1;
260                *step
261            },
262            location: state.current_location.clone().unwrap_or_else(|| DebugLocation {
263                module: "runtime".to_string(),
264                function: "step".to_string(),
265                line: None,
266                instruction: Some(format!("Step {:?}", mode)),
267                context: None,
268            }),
269            call_stack: state.call_stack.clone(),
270            variables: state.variables.clone(),
271            memory_usage: None,
272            performance_metrics: HashMap::new(),
273        };
274
275        // Add to history
276        {
277            let mut history = self.execution_history.lock();
278            history.push_back(snapshot.clone());
279            if history.len() > self.max_history_size {
280                history.pop_front();
281            }
282        }
283
284        Ok(DebuggerResponse::Stepped(snapshot))
285    }
286
287    /// Set a breakpoint
288    async fn set_breakpoint(
289        &self,
290        location: DebugLocation,
291        condition: Option<String>,
292    ) -> Result<DebuggerResponse> {
293        let breakpoint = Breakpoint {
294            id: Uuid::new_v4(),
295            location,
296            condition,
297            hit_count: 0,
298            enabled: true,
299            temporary: false,
300            log_message: None,
301            created_at: Utc::now(),
302        };
303
304        self.breakpoints.write().insert(breakpoint.id.to_string(), breakpoint.clone());
305
306        tracing::info!(
307            "Breakpoint set at {}::{}",
308            breakpoint.location.module,
309            breakpoint.location.function
310        );
311        Ok(DebuggerResponse::Started) // Use Started as generic success
312    }
313
314    /// Remove a breakpoint
315    async fn remove_breakpoint(&self, id: Uuid) -> Result<DebuggerResponse> {
316        if self.breakpoints.write().remove(&id.to_string()).is_some() {
317            tracing::info!("Breakpoint {} removed", id);
318        }
319        Ok(DebuggerResponse::Started)
320    }
321
322    /// Inspect a variable
323    async fn inspect_variable(&self, name: &str) -> Result<DebuggerResponse> {
324        let state = self.state.read();
325        if let Some(var) = state.variables.get(name) {
326            Ok(DebuggerResponse::VariableInspected(var.clone()))
327        } else {
328            Ok(DebuggerResponse::Error(format!(
329                "Variable '{}' not found",
330                name
331            )))
332        }
333    }
334
335    /// Evaluate an expression
336    async fn evaluate_expression(&self, _expr: &str) -> Result<DebuggerResponse> {
337        // Simplified expression evaluation - in a real implementation,
338        // this would parse and evaluate the expression
339        Ok(DebuggerResponse::ExpressionEvaluated(
340            "Expression evaluation not implemented".to_string(),
341        ))
342    }
343
344    /// Jump to a specific step in history (time-travel debugging)
345    async fn jump_to_step(&self, step_num: usize) -> Result<DebuggerResponse> {
346        let history = self.execution_history.lock();
347        if let Some(snapshot) = history.iter().find(|s| s.step_number == step_num) {
348            let snapshot = snapshot.clone();
349            drop(history);
350
351            // Restore state from snapshot
352            {
353                let mut state = self.state.write();
354                state.current_location = Some(snapshot.location.clone());
355                state.call_stack = snapshot.call_stack.clone();
356                state.variables = snapshot.variables.clone();
357                state.is_paused = true;
358            }
359
360            *self.current_step.lock() = step_num;
361            Ok(DebuggerResponse::JumpedToStep(snapshot))
362        } else {
363            Ok(DebuggerResponse::Error(format!(
364                "Step {} not found in history",
365                step_num
366            )))
367        }
368    }
369
370    /// Reset the debugger state
371    pub async fn reset(&self) -> Result<DebuggerResponse> {
372        {
373            let mut state = self.state.write();
374            *state = DebuggerState {
375                is_running: false,
376                is_paused: false,
377                current_location: None,
378                call_stack: Vec::new(),
379                variables: IndexMap::new(),
380                step_mode: StepMode::Continue,
381                session_start: Utc::now(),
382            };
383        }
384
385        self.breakpoints.write().clear();
386        self.execution_history.lock().clear();
387        *self.current_step.lock() = 0;
388
389        Ok(DebuggerResponse::Reset)
390    }
391
392    /// Add a variable to the current scope
393    pub async fn add_variable(&self, name: String, value: String, type_name: String) -> Result<()> {
394        let var = VariableValue {
395            name: name.clone(),
396            value,
397            type_name,
398            size_bytes: None,
399            shape: None,
400            is_tensor: false,
401            metadata: HashMap::new(),
402        };
403
404        self.state.write().variables.insert(name, var);
405        Ok(())
406    }
407
408    /// Update current execution location
409    pub async fn update_location(&self, location: DebugLocation) -> Result<()> {
410        let mut state = self.state.write();
411        state.current_location = Some(location.clone());
412
413        // Check if we hit a breakpoint
414        let breakpoints = self.breakpoints.read();
415        for (_, breakpoint) in breakpoints.iter() {
416            if breakpoint.enabled
417                && breakpoint.location.module == location.module
418                && breakpoint.location.function == location.function
419            {
420                state.is_paused = true;
421                tracing::info!(
422                    "Breakpoint hit at {}::{}",
423                    location.module,
424                    location.function
425                );
426                break;
427            }
428        }
429
430        Ok(())
431    }
432
433    /// Push a new frame onto the call stack
434    pub async fn push_frame(&self, location: DebugLocation) -> Result<()> {
435        let frame = StackFrame {
436            id: Uuid::new_v4(),
437            location,
438            locals: IndexMap::new(),
439            timestamp: Utc::now(),
440            depth: self.state.read().call_stack.len(),
441        };
442
443        self.state.write().call_stack.push(frame);
444        Ok(())
445    }
446
447    /// Pop a frame from the call stack
448    pub async fn pop_frame(&self) -> Result<Option<StackFrame>> {
449        Ok(self.state.write().call_stack.pop())
450    }
451
452    /// Generate debugger report
453    pub async fn generate_report(&self) -> Result<InteractiveDebuggerReport> {
454        let state = self.state.read();
455        let breakpoints = self.breakpoints.read();
456        let history = self.execution_history.lock();
457
458        Ok(InteractiveDebuggerReport {
459            session_duration: Utc::now() - state.session_start,
460            total_steps: *self.current_step.lock(),
461            total_breakpoints: breakpoints.len(),
462            breakpoint_hits: breakpoints.values().map(|b| b.hit_count).sum(),
463            max_call_stack_depth: state.call_stack.len(),
464            variables_tracked: state.variables.len(),
465            history_entries: history.len(),
466            current_state: state.clone(),
467        })
468    }
469
470    /// Check if debugger is currently paused
471    pub fn is_paused(&self) -> bool {
472        self.state.read().is_paused
473    }
474
475    /// Get current step number
476    pub fn current_step(&self) -> usize {
477        *self.current_step.lock()
478    }
479
480    /// Get active breakpoints
481    pub fn get_breakpoints(&self) -> Vec<Breakpoint> {
482        self.breakpoints.read().values().cloned().collect()
483    }
484}
485
486/// Report generated by the interactive debugger
487#[derive(Debug, Clone, Serialize, Deserialize)]
488pub struct InteractiveDebuggerReport {
489    pub session_duration: chrono::Duration,
490    pub total_steps: usize,
491    pub total_breakpoints: usize,
492    pub breakpoint_hits: usize,
493    pub max_call_stack_depth: usize,
494    pub variables_tracked: usize,
495    pub history_entries: usize,
496    pub current_state: DebuggerState,
497}
498
499impl Default for DebuggerState {
500    fn default() -> Self {
501        Self {
502            is_running: false,
503            is_paused: false,
504            current_location: None,
505            call_stack: Vec::new(),
506            variables: IndexMap::new(),
507            step_mode: StepMode::Continue,
508            session_start: Utc::now(),
509        }
510    }
511}