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