Skip to main content

ghostscope_ui/events/
runtime.rs

1use super::debug_info::{
2    SectionInfo, SharedLibraryInfo, SourceCodeInfo, SourceFileGroup, TargetDebugInfo,
3};
4use super::source_path::SourcePathInfo;
5use unicode_width::UnicodeWidthStr;
6
7/// Trace status enumeration for shared use between UI and runtime
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub enum TraceStatus {
10    Active,
11    Disabled,
12    Failed,
13}
14
15impl std::fmt::Display for TraceStatus {
16    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17        match self {
18            TraceStatus::Active => write!(f, "Active"),
19            TraceStatus::Disabled => write!(f, "Disabled"),
20            TraceStatus::Failed => write!(f, "Failed"),
21        }
22    }
23}
24
25impl TraceStatus {
26    /// Convert to emoji representation
27    pub fn to_emoji(&self) -> String {
28        match self {
29            TraceStatus::Active => "βœ…".to_string(),
30            TraceStatus::Disabled => "⏸️".to_string(),
31            TraceStatus::Failed => "❌".to_string(),
32        }
33    }
34
35    /// Parse from string (for backward compatibility)
36    pub fn from_string(s: &str) -> Self {
37        match s {
38            "Active" => TraceStatus::Active,
39            "Disabled" => TraceStatus::Disabled,
40            "Failed" => TraceStatus::Failed,
41            _ => TraceStatus::Failed, // Default to Failed for unknown status
42        }
43    }
44}
45
46/// Commands that TUI can send to runtime
47#[derive(Debug, Clone)]
48pub enum RuntimeCommand {
49    ExecuteScript {
50        command: String,
51        selected_index: Option<usize>,
52    },
53    RequestSourceCode, // Request source code for current function/address
54    DisableTrace(u32), // Disable specific trace by ID
55    EnableTrace(u32),  // Enable specific trace by ID
56    DisableAllTraces,  // Disable all traces
57    EnableAllTraces,   // Enable all traces
58    DeleteTrace(u32),  // Completely delete specific trace and all resources
59    DeleteAllTraces,   // Delete all traces and resources
60    InfoFunction {
61        target: String,
62        verbose: bool,
63    }, // Get debug info for a function by name
64    InfoLine {
65        target: String,
66        verbose: bool,
67    }, // Get debug info for a source line (file:line)
68    InfoAddress {
69        target: String,
70        verbose: bool,
71    }, // Get debug info for a memory address (TODO: not implemented yet)
72    InfoTrace {
73        trace_id: Option<u32>,
74    }, // Get info for one/all traces (individual messages)
75    InfoTraceAll,
76    InfoSource, // Get all source files information
77    InfoShare,  // Get shared library information (like GDB's "info share")
78    InfoFile,   // Get executable file information and sections (like GDB's "info file")
79    SaveTraces {
80        filename: Option<String>,
81        filter: crate::components::command_panel::trace_persistence::SaveFilter,
82    }, // Save traces to a file
83    LoadTraces {
84        filename: String,
85        traces: Vec<TraceDefinition>,
86    }, // Load traces from a file
87    SrcPathList,
88    SrcPathAddDir {
89        dir: String,
90    },
91    SrcPathAddMap {
92        from: String,
93        to: String,
94    },
95    SrcPathRemove {
96        pattern: String,
97    },
98    SrcPathClear,
99    SrcPathReset,
100    Shutdown,
101}
102
103/// Definition of a trace to be loaded
104#[derive(Debug, Clone)]
105pub struct TraceDefinition {
106    pub target: String,
107    pub script: String,
108    pub enabled: bool,
109    pub selected_index: Option<usize>,
110}
111
112/// Result of loading a single trace
113#[derive(Debug, Clone)]
114pub struct TraceLoadDetail {
115    pub target: String,
116    pub trace_id: Option<u32>,
117    pub status: LoadStatus,
118    pub error: Option<String>,
119}
120
121/// Status of loading a trace
122#[derive(Debug, Clone)]
123pub enum LoadStatus {
124    Created,         // Successfully created and enabled
125    CreatedDisabled, // Created but disabled
126    Failed,          // Failed to create
127    Skipped,         // Skipped (e.g., duplicate)
128}
129
130/// Execution status for individual script targets
131#[derive(Debug, Clone)]
132pub enum ExecutionStatus {
133    Success,
134    Failed(String),  // Contains error message
135    Skipped(String), // Contains reason for skipping
136}
137
138/// Result of executing a single script target (PC/function)
139#[derive(Debug, Clone)]
140pub struct ScriptExecutionResult {
141    pub pc_address: u64,
142    pub target_name: String,
143    pub binary_path: String, // Full path to the binary
144    pub status: ExecutionStatus,
145    pub source_file: Option<String>,
146    pub source_line: Option<u32>,
147    pub is_inline: Option<bool>,
148}
149
150/// Detailed compilation result for a script with multiple targets
151#[derive(Debug, Clone)]
152pub struct ScriptCompilationDetails {
153    pub trace_ids: Vec<u32>, // List of generated trace IDs (one per successful compilation)
154    pub results: Vec<ScriptExecutionResult>,
155    pub total_count: usize,
156    pub success_count: usize,
157    pub failed_count: usize,
158}
159
160#[derive(Debug, Clone)]
161pub enum RuntimeStatus {
162    DwarfLoadingStarted,
163    DwarfLoadingCompleted {
164        symbols_count: usize,
165    },
166    DwarfLoadingFailed(String),
167    ScriptCompilationCompleted {
168        details: ScriptCompilationDetails, // Contains trace_ids, success/failed counts and results
169    },
170    UprobeAttached {
171        function: String,
172        address: u64,
173    },
174    UprobeDetached {
175        function: String,
176    },
177    SourceCodeLoaded(SourceCodeInfo),
178    SourceCodeLoadFailed(String),
179    TraceEnabled {
180        trace_id: u32,
181    },
182    TraceDisabled {
183        trace_id: u32,
184    },
185    AllTracesEnabled {
186        count: usize,
187        error: Option<String>, // Error message if operation completely failed
188    },
189    AllTracesDisabled {
190        count: usize,
191        error: Option<String>, // Error message if operation completely failed
192    },
193    TraceEnableFailed {
194        trace_id: u32,
195        error: String,
196    },
197    TraceDisableFailed {
198        trace_id: u32,
199        error: String,
200    },
201    TraceDeleted {
202        trace_id: u32,
203    },
204    AllTracesDeleted {
205        count: usize,
206        error: Option<String>, // Error message if operation completely failed
207    },
208    TraceDeleteFailed {
209        trace_id: u32,
210        error: String,
211    },
212    InfoFunctionResult {
213        target: String,
214        info: TargetDebugInfo,
215        verbose: bool,
216    },
217    InfoFunctionFailed {
218        target: String,
219        error: String,
220    },
221    InfoLineResult {
222        target: String,
223        info: TargetDebugInfo,
224        verbose: bool,
225    },
226    InfoLineFailed {
227        target: String,
228        error: String,
229    },
230    InfoAddressResult {
231        target: String,
232        info: TargetDebugInfo,
233        verbose: bool,
234    },
235    InfoAddressFailed {
236        target: String,
237        error: String,
238    },
239    /// Detailed info for a trace (summary + PC)
240    TraceInfo {
241        trace_id: u32,
242        target: String,
243        status: TraceStatus,
244        pid: Option<u32>,
245        host_pid: Option<u32>,
246        binary: String,
247        script_preview: Option<String>,
248        pc: u64,
249    },
250    /// All trace info with structured data for UI rendering
251    TraceInfoAll {
252        summary: TraceSummaryInfo,
253        traces: Vec<TraceDetailInfo>,
254    },
255    /// Failed to get info for a specific trace
256    TraceInfoFailed {
257        trace_id: u32,
258        error: String,
259    },
260    /// Source file information response (grouped by module)
261    FileInfo {
262        groups: Vec<SourceFileGroup>,
263    },
264    /// Failed to get file information
265    FileInfoFailed {
266        error: String,
267    },
268    /// Traces saved to file successfully
269    TracesSaved {
270        filename: String,
271        saved_count: usize,
272        total_count: usize,
273    },
274    /// Failed to save traces
275    TracesSaveFailed {
276        error: String,
277    },
278    /// Traces loaded from file successfully
279    TracesLoaded {
280        filename: String,
281        total_count: usize,
282        success_count: usize,
283        failed_count: usize,
284        disabled_count: usize,
285        details: Vec<TraceLoadDetail>,
286    },
287    /// Failed to load traces
288    TracesLoadFailed {
289        filename: String,
290        error: String,
291    },
292    /// Shared library information response
293    ShareInfo {
294        libraries: Vec<SharedLibraryInfo>,
295    },
296    /// Failed to get shared library information
297    ShareInfoFailed {
298        error: String,
299    },
300    /// Executable file information response
301    ExecutableFileInfo {
302        file_path: String,
303        file_type: String,
304        entry_point: Option<u64>,
305        has_symbols: bool,
306        has_debug_info: bool,
307        debug_file_path: Option<String>,
308        text_section: Option<SectionInfo>,
309        data_section: Option<SectionInfo>,
310        mode_description: String,
311    },
312    /// Failed to get executable file information
313    ExecutableFileInfoFailed {
314        error: String,
315    },
316    // Module-level loading progress (new)
317    DwarfModuleDiscovered {
318        module_path: String,
319        total_modules: usize,
320    },
321    DwarfModuleLoadingStarted {
322        module_path: String,
323        current: usize,
324        total: usize,
325    },
326    DwarfModuleLoadingCompleted {
327        module_path: String,
328        stats: ModuleLoadingStats,
329        current: usize,
330        total: usize,
331    },
332    DwarfModuleLoadingFailed {
333        module_path: String,
334        error: String,
335        current: usize,
336        total: usize,
337    },
338    SrcPathInfo {
339        info: SourcePathInfo,
340    },
341    SrcPathUpdated {
342        message: String,
343    },
344    SrcPathFailed {
345        error: String,
346    },
347    /// Runtime->UI trace channel backpressure warning (events dropped)
348    TraceBackpressure {
349        dropped_since_last: u64,
350        dropped_total: u64,
351        queue_capacity: usize,
352    },
353    /// eBPF program failed to write events into the kernel output buffer.
354    EbpfOutputLoss {
355        trace_id: u32,
356        target_display: String,
357        lost_since_last: u64,
358        lost_total: u64,
359    },
360}
361
362/// Statistics for a loaded module
363#[derive(Debug, Clone)]
364pub struct ModuleLoadingStats {
365    pub functions: usize,
366    pub variables: usize,
367    pub types: usize,
368    pub debug_source: String,
369    pub debug_source_path: Option<String>,
370    pub load_time_ms: u64,
371}
372
373/// Summary information for all traces
374#[derive(Debug, Clone)]
375pub struct TraceSummaryInfo {
376    pub total: usize,
377    pub active: usize,
378    pub disabled: usize,
379}
380
381/// Detailed information for a specific trace
382#[derive(Debug, Clone)]
383pub struct TraceDetailInfo {
384    pub trace_id: u32,
385    pub target_display: String,
386    pub binary_path: String,
387    pub pc: u64,
388    pub status: TraceStatus,
389    pub duration: String, // "5m32s", "1h5m", etc.
390}
391
392impl TraceDetailInfo {
393    /// Format trace info line with binary path and PC information
394    pub fn format_line(&self) -> String {
395        // Extract binary name from path for cleaner display
396        let binary_name = std::path::Path::new(&self.binary_path)
397            .file_name()
398            .and_then(|name| name.to_str())
399            .unwrap_or(&self.binary_path);
400
401        format!(
402            "#{} | {}+0x{:x} | {} ({}) ",
403            self.trace_id, binary_name, self.pc, self.target_display, self.status
404        )
405    }
406}
407
408impl RuntimeStatus {
409    /// Format TraceInfo for enhanced display
410    pub fn format_trace_info(&self) -> Option<String> {
411        match self {
412            RuntimeStatus::TraceInfo {
413                trace_id,
414                target,
415                status,
416                pid,
417                host_pid,
418                binary,
419                script_preview,
420                pc,
421            } => {
422                // Header line
423                let mut result =
424                    format!("πŸ”Ž Trace [{}] {} {}\n", trace_id, status.to_emoji(), status);
425
426                // Collect fields for aligned key-value formatting
427                let binary_name = std::path::Path::new(binary)
428                    .file_name()
429                    .and_then(|name| name.to_str())
430                    .unwrap_or(binary);
431
432                let mut fields: Vec<(&str, String)> = Vec::new();
433                fields.push(("🎯 Target", target.clone()));
434                fields.push(("πŸ“¦ Binary", binary.clone()));
435                fields.push(("πŸ“ Address", format!("{binary_name}+0x{pc:x}")));
436                match (pid, host_pid) {
437                    (Some(proc_pid), Some(host_pid_val)) if proc_pid != host_pid_val => {
438                        fields.push(("🏷️ PID(proc)", proc_pid.to_string()));
439                        fields.push(("🏷️ PID(host)", host_pid_val.to_string()));
440                    }
441                    (Some(proc_pid), _) => {
442                        fields.push(("🏷️ PID", proc_pid.to_string()));
443                    }
444                    (None, Some(host_pid_val)) => {
445                        fields.push(("🏷️ PID(host)", host_pid_val.to_string()));
446                    }
447                    (None, None) => {}
448                }
449                if let Some(ref script) = script_preview {
450                    fields.push(("πŸ“ Script", script.clone()));
451                }
452
453                // Compute max key width (accounting for emoji display width)
454                let max_key_width = fields.iter().map(|(k, _)| k.width()).max().unwrap_or(0);
455
456                for (key, value) in fields {
457                    let key_width = key.width();
458                    let pad = max_key_width.saturating_sub(key_width);
459                    let spaces = " ".repeat(pad);
460                    result.push_str(&format!("  {key}{spaces}: {value}\n"));
461                }
462
463                Some(result)
464            }
465            _ => None,
466        }
467    }
468
469    /// Styled version of TraceInfo for display
470    pub fn format_trace_info_styled(&self) -> Option<Vec<ratatui::text::Line<'static>>> {
471        use crate::components::command_panel::style_builder::{StylePresets, StyledLineBuilder};
472        use ratatui::text::Line;
473
474        match self {
475            RuntimeStatus::TraceInfo {
476                trace_id,
477                target,
478                status,
479                pid,
480                host_pid,
481                binary,
482                script_preview: _,
483                pc,
484            } => {
485                let mut lines = Vec::new();
486
487                // Title
488                lines.push(
489                    StyledLineBuilder::new()
490                        .title(format!(
491                            "πŸ”Ž Trace [{}] {} {}",
492                            trace_id,
493                            status.to_emoji(),
494                            status
495                        ))
496                        .build(),
497                );
498
499                let binary_name = std::path::Path::new(binary)
500                    .file_name()
501                    .and_then(|name| name.to_str())
502                    .unwrap_or(binary)
503                    .to_string();
504
505                lines.push(
506                    StyledLineBuilder::new()
507                        .text("  ")
508                        .key("🎯 Target:")
509                        .text(" ")
510                        .value(target)
511                        .build(),
512                );
513                lines.push(
514                    StyledLineBuilder::new()
515                        .text("  ")
516                        .key("πŸ“¦ Binary:")
517                        .text(" ")
518                        .value(binary)
519                        .build(),
520                );
521                lines.push(
522                    StyledLineBuilder::new()
523                        .text("  ")
524                        .key("πŸ“ Address:")
525                        .text(" ")
526                        .value(format!("{binary_name}+0x{pc:x}"))
527                        .build(),
528                );
529
530                match (pid, host_pid) {
531                    (Some(proc_pid), Some(host_pid_val)) if proc_pid != host_pid_val => {
532                        lines.push(
533                            StyledLineBuilder::new()
534                                .text("  ")
535                                .key("🏷️ PID(proc):")
536                                .text(" ")
537                                .value(proc_pid.to_string())
538                                .build(),
539                        );
540                        lines.push(
541                            StyledLineBuilder::new()
542                                .text("  ")
543                                .key("🏷️ PID(host):")
544                                .text(" ")
545                                .value(host_pid_val.to_string())
546                                .build(),
547                        );
548                    }
549                    (Some(proc_pid), _) => {
550                        lines.push(
551                            StyledLineBuilder::new()
552                                .text("  ")
553                                .key("🏷️ PID:")
554                                .text(" ")
555                                .value(proc_pid.to_string())
556                                .build(),
557                        );
558                    }
559                    (None, Some(host_pid_val)) => {
560                        lines.push(
561                            StyledLineBuilder::new()
562                                .text("  ")
563                                .key("🏷️ PID(host):")
564                                .text(" ")
565                                .value(host_pid_val.to_string())
566                                .build(),
567                        );
568                    }
569                    (None, None) => {}
570                }
571
572                Some(lines)
573            }
574            RuntimeStatus::TraceInfoAll { summary, traces } => {
575                let mut lines = Vec::new();
576                // Title
577                lines.push(
578                    StyledLineBuilder::new()
579                        .title(format!(
580                            "πŸ” All Traces ({} total, {} active):",
581                            summary.total, summary.active
582                        ))
583                        .build(),
584                );
585                lines.push(Line::from(""));
586
587                for t in traces {
588                    let binary_name = std::path::Path::new(&t.binary_path)
589                        .file_name()
590                        .and_then(|name| name.to_str())
591                        .unwrap_or(&t.binary_path)
592                        .to_string();
593                    let status_style = match t.status {
594                        TraceStatus::Active => StylePresets::SUCCESS,
595                        TraceStatus::Disabled => StylePresets::LOCATION,
596                        TraceStatus::Failed => StylePresets::ERROR,
597                    };
598                    let line = StyledLineBuilder::new()
599                        .text("  ")
600                        .styled(format!("#{}", t.trace_id), StylePresets::ADDRESS)
601                        .text("  | ")
602                        .styled(format!("{}+0x{:x}", binary_name, t.pc), StylePresets::KEY)
603                        .text("  | ")
604                        .value(&t.target_display)
605                        .text("  (")
606                        .styled(t.status.to_string(), status_style)
607                        .text(")")
608                        .build();
609                    lines.push(line);
610                }
611
612                Some(lines)
613            }
614            _ => None,
615        }
616    }
617}