Skip to main content

calltrace/
json_output.rs

1//! JSON Output Generation
2//!
3//! This module generates structured JSON output for call trees with full
4//! argument information and thread relationships.
5
6use crate::call_tree::{CallTreeManager, NodeId, ThreadCallTree};
7use crate::error::{CallTraceError, Result};
8use crate::register_reader::{ArgumentValue, CapturedArgument};
9use libc;
10use serde::{Deserialize, Serialize};
11use std::fs::File;
12use std::io::Write;
13
14/// Complete trace session output
15#[derive(Debug, Serialize, Deserialize)]
16pub struct TraceSession {
17    pub metadata: SessionMetadata,
18    pub threads: Vec<ThreadTrace>,
19    pub thread_relationships: Vec<ThreadRelationship>,
20    pub statistics: SessionStatistics,
21    #[serde(skip_serializing_if = "Option::is_none")]
22    pub crash: Option<CrashInfo>,
23}
24
25/// Crash information for JSON output
26#[derive(Debug, Serialize, Deserialize)]
27pub struct CrashInfo {
28    pub signal: i32,
29    pub signal_name: String,
30    pub thread_id: u64,
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub fault_address: Option<String>,
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub instruction_pointer: Option<String>,
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub stack_pointer: Option<String>,
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub register_context: Option<crate::register_reader::RegisterContext>,
39    pub backtrace: Vec<StackFrame>,
40    pub crash_time: String,
41    pub crash_timestamp: f64,
42}
43
44/// Stack frame for JSON output
45#[derive(Debug, Serialize, Deserialize)]
46pub struct StackFrame {
47    pub address: String,
48    #[serde(skip_serializing_if = "Option::is_none")]
49    pub function_name: Option<String>,
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub library_name: Option<String>,
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub offset: Option<String>,
54}
55
56/// Session metadata
57#[derive(Debug, Serialize, Deserialize)]
58pub struct SessionMetadata {
59    pub start_time: String,
60    pub end_time: String,
61    pub duration_ms: f64,
62    pub process_info: ProcessInfo,
63    pub calltrace_version: String,
64}
65
66/// Process information
67#[derive(Debug, Serialize, Deserialize)]
68pub struct ProcessInfo {
69    pub pid: u32,
70    pub architecture: String,
71    pub executable_path: Option<String>,
72    pub environment_variables: std::collections::HashMap<String, String>,
73}
74
75/// Per-thread trace information
76#[derive(Debug, Serialize, Deserialize)]
77pub struct ThreadTrace {
78    pub thread_id: u64,
79    pub thread_name: String,
80    pub is_main_thread: bool,
81    pub start_time: String,
82    pub end_time: Option<String>,
83    pub parent_thread_id: Option<u64>,
84    pub creation_function: Option<String>,
85    pub statistics: ThreadStatistics,
86    pub call_tree: Option<CallNodeJson>,
87}
88
89/// Thread relationship information for JSON
90#[derive(Debug, Serialize, Deserialize)]
91pub struct ThreadRelationship {
92    pub parent_thread_id: u64,
93    pub child_thread_id: u64,
94    pub creation_function: String,
95    pub creation_time: String,
96}
97
98/// Call tree node in JSON format
99#[derive(Debug, Serialize, Deserialize)]
100pub struct CallNodeJson {
101    pub id: u32,
102    pub function: String,
103    pub address: String,
104    pub call_site: String,
105    pub start_time: String,
106    pub end_time: Option<String>,
107    pub duration_us: Option<u64>,
108    pub call_depth: usize,
109    pub signature: Option<String>,
110    pub arguments: Vec<ArgumentJson>,
111    pub return_value: Option<ArgumentValueJson>,
112    pub children: Vec<CallNodeJson>,
113
114    // Thread creation info
115    pub is_pthread_create: bool,
116    pub created_thread_id: Option<u64>,
117
118    // Source location (if available)
119    pub source_file: Option<String>,
120    pub line_number: Option<u32>,
121}
122
123/// Function argument in JSON format
124#[derive(Debug, Serialize, Deserialize)]
125pub struct ArgumentJson {
126    pub name: String,
127    pub type_name: String,
128    pub value: ArgumentValueJson,
129    pub location: ArgumentLocationJson,
130}
131
132/// Argument value in JSON format
133#[derive(Debug, Serialize, Deserialize)]
134#[serde(tag = "type", content = "value")]
135pub enum ArgumentValueJson {
136    Integer(u64),
137    Float(f32),
138    Double(f64),
139    Pointer {
140        address: String,
141        string_value: Option<String>,
142    },
143    String(String),
144    Raw(String),    // Hex-encoded raw bytes
145    Object(String), // Complex types (struct, array, union) as string description
146    Error(String),
147}
148
149/// Argument location information
150#[derive(Debug, Serialize, Deserialize)]
151pub struct ArgumentLocationJson {
152    pub class: String, // "integer", "sse", "memory"
153    pub register_index: Option<usize>,
154    pub stack_offset: Option<usize>,
155    pub size: usize,
156}
157
158/// Thread statistics
159#[derive(Debug, Serialize, Deserialize)]
160pub struct ThreadStatistics {
161    pub total_function_calls: u64,
162    pub max_call_depth: usize,
163    pub total_errors: u64,
164}
165
166/// Session statistics
167#[derive(Debug, Serialize, Deserialize)]
168pub struct SessionStatistics {
169    pub total_threads: u64,
170    pub total_function_calls: u64,
171    pub total_nodes: u64,
172    pub session_duration_us: u64,
173}
174
175/// JSON output generator
176pub struct JsonOutputGenerator {
177    pretty_print: bool,
178    include_arguments: bool,
179    include_source_info: bool,
180}
181
182impl JsonOutputGenerator {
183    /// Create a new JSON output generator
184    pub fn new() -> Self {
185        Self {
186            pretty_print: true,
187            include_arguments: true,
188            include_source_info: true,
189        }
190    }
191
192    /// Configure output options
193    pub fn configure(
194        &mut self,
195        pretty_print: bool,
196        include_arguments: bool,
197        include_source_info: bool,
198    ) {
199        self.pretty_print = pretty_print;
200        self.include_arguments = include_arguments;
201        self.include_source_info = include_source_info;
202    }
203
204    /// Generate JSON output from call tree manager
205    pub fn generate_output(&self, manager: &CallTreeManager) -> Result<TraceSession> {
206        let stats = manager.get_statistics();
207
208        // Generate session metadata
209        let current_time_us = std::time::SystemTime::now()
210            .duration_since(std::time::UNIX_EPOCH)
211            .unwrap_or_default()
212            .as_micros() as u64;
213        let session_start_us = current_time_us.saturating_sub(stats.session_duration_us);
214
215        let metadata = SessionMetadata {
216            start_time: format_timestamp(session_start_us),
217            end_time: format_timestamp(current_time_us),
218            duration_ms: stats.session_duration_us as f64 / 1000.0,
219            process_info: ProcessInfo {
220                pid: std::process::id(),
221                architecture: crate::string_constants::X86_64.to_string(),
222                executable_path: None, // Could be obtained from /proc/self/exe
223                environment_variables: collect_environment_variables(),
224            },
225            calltrace_version: env!("CARGO_PKG_VERSION").to_string(),
226        };
227
228        // Generate thread traces
229        let mut threads = Vec::new();
230        let thread_ids = manager.get_thread_ids();
231
232        for thread_id in thread_ids {
233            if let Some(thread_tree) = manager.get_thread_tree(thread_id) {
234                let tree = thread_tree.read().unwrap();
235                let thread_trace = self.generate_thread_trace(&tree, manager)?;
236                threads.push(thread_trace);
237            }
238        }
239
240        // Generate thread relationships
241        let thread_relationships = Vec::new(); // TODO: Extract from manager
242
243        // Generate session statistics
244        let session_stats = SessionStatistics {
245            total_threads: stats.total_threads,
246            total_function_calls: 0, // TODO: Sum from all threads
247            total_nodes: stats.total_nodes,
248            session_duration_us: stats.session_duration_us,
249        };
250
251        Ok(TraceSession {
252            metadata,
253            threads,
254            thread_relationships,
255            statistics: session_stats,
256            crash: None,
257        })
258    }
259
260    /// Generate trace information for a single thread
261    fn generate_thread_trace(
262        &self,
263        thread_tree: &ThreadCallTree,
264        manager: &CallTreeManager,
265    ) -> Result<ThreadTrace> {
266        let call_tree = if let Some(root_id) = thread_tree.root_node {
267            self.generate_call_node_json(root_id, manager)?
268        } else {
269            None
270        };
271
272        Ok(ThreadTrace {
273            thread_id: thread_tree.thread_id,
274            thread_name: thread_tree.thread_name.clone(),
275            is_main_thread: thread_tree.is_main_thread,
276            start_time: format_timestamp(thread_tree.start_time),
277            end_time: thread_tree.end_time.map(format_timestamp),
278            parent_thread_id: thread_tree.parent_thread_id,
279            creation_function: thread_tree.creation_function.clone(),
280            statistics: ThreadStatistics {
281                total_function_calls: thread_tree.total_calls,
282                max_call_depth: thread_tree.max_depth,
283                total_errors: thread_tree.total_errors,
284            },
285            call_tree,
286        })
287    }
288
289    /// Generate JSON representation of a call node and its children
290    fn generate_call_node_json(
291        &self,
292        node_id: NodeId,
293        manager: &CallTreeManager,
294    ) -> Result<Option<CallNodeJson>> {
295        let node_ref = manager.get_any_node(node_id).ok_or_else(|| {
296            CallTraceError::InvalidArgument(format!("Node {} not found", node_id))
297        })?;
298
299        let node = node_ref.read().unwrap();
300
301        // Generate arguments JSON
302        let arguments = if self.include_arguments {
303            node.arguments
304                .iter()
305                .map(|arg| self.convert_argument_to_json(arg))
306                .collect()
307        } else {
308            Vec::new()
309        };
310
311        // Generate return value JSON
312        let return_value = if self.include_arguments {
313            node.return_value
314                .as_ref()
315                .map(|rv| self.convert_return_value_to_json(rv))
316        } else {
317            None
318        };
319
320        // Generate children recursively
321        let mut children = Vec::new();
322        for &child_id in &node.children {
323            if let Some(child_json) = self.generate_call_node_json(child_id, manager)? {
324                children.push(child_json);
325            }
326        }
327
328        // Extract source information if available
329        let (source_file, line_number) = if self.include_source_info {
330            node.function_info.as_ref().map_or((None, None), |info| {
331                (info.source_file.clone(), info.line_number)
332            })
333        } else {
334            (None, None)
335        };
336
337        // Use function name if available, otherwise keep the address but make it cleaner
338        let function_display_name = if !node.function_name.starts_with("0x") {
339            // We have a real function name from DWARF or dladdr
340            node.function_name.clone()
341        } else {
342            // For addresses, try to make them more readable by showing offset from base
343            crate::format_address_with_prefix("func", node.function_address & 0xFFFF)
344        };
345
346        Ok(Some(CallNodeJson {
347            id: node.id,
348            function: function_display_name,
349            address: crate::format_address(node.function_address),
350            call_site: crate::format_address(node.call_site),
351            start_time: format_timestamp(node.start_time),
352            end_time: node.end_time.map(format_timestamp),
353            duration_us: node.duration_us,
354            call_depth: node.call_depth,
355            signature: node.function_info.as_ref().map(|_| "TODO".to_string()), // TODO: Extract signature
356            arguments,
357            return_value,
358            children,
359            is_pthread_create: node.is_pthread_create,
360            created_thread_id: node.created_thread_id,
361            source_file,
362            line_number,
363        }))
364    }
365
366    /// Convert a return value to JSON format
367    fn convert_return_value_to_json(&self, return_value: &ArgumentValue) -> ArgumentValueJson {
368        match return_value {
369            ArgumentValue::Integer(val) => ArgumentValueJson::Integer(*val),
370            ArgumentValue::Float(val) => ArgumentValueJson::Float(*val),
371            ArgumentValue::Double(val) => ArgumentValueJson::Double(*val),
372            ArgumentValue::Pointer(addr) => ArgumentValueJson::Pointer {
373                address: crate::format_address(*addr),
374                string_value: None, // TODO: Extract string value if available
375            },
376            ArgumentValue::String(s) => ArgumentValueJson::String(s.clone()),
377            ArgumentValue::Raw(bytes) => ArgumentValueJson::Raw(hex::encode(bytes)),
378            ArgumentValue::Struct {
379                type_name,
380                members: _,
381                size,
382            } => {
383                // TODO: Full struct serialization
384                ArgumentValueJson::Object(format!("struct {} (size: {})", type_name, size))
385            }
386            ArgumentValue::Array {
387                element_type,
388                elements: _,
389                count,
390                element_size,
391            } => {
392                // TODO: Full array serialization
393                ArgumentValueJson::Object(format!(
394                    "{}[{}] (element_size: {})",
395                    element_type, count, element_size
396                ))
397            }
398            ArgumentValue::Union {
399                type_name,
400                raw_data: _,
401                size,
402            } => ArgumentValueJson::Object(format!("union {} (size: {})", type_name, size)),
403            ArgumentValue::Null => ArgumentValueJson::Pointer {
404                address: crate::string_constants::NULL_ADDRESS.to_string(),
405                string_value: Some(crate::string_constants::NULL_STRING.to_string()),
406            },
407            ArgumentValue::Unknown {
408                type_name,
409                raw_data,
410                error,
411            } => {
412                let error_info = error
413                    .as_ref()
414                    .map(|e| format!(": {}", e))
415                    .unwrap_or_default();
416                ArgumentValueJson::Object(format!(
417                    "unknown {} (size: {}){}",
418                    type_name,
419                    raw_data.len(),
420                    error_info
421                ))
422            }
423        }
424    }
425
426    /// Convert a captured argument to JSON format
427    fn convert_argument_to_json(&self, arg: &CapturedArgument) -> ArgumentJson {
428        let value_json = if arg.valid {
429            match &arg.value {
430                ArgumentValue::Integer(val) => ArgumentValueJson::Integer(*val),
431                ArgumentValue::Float(val) => ArgumentValueJson::Float(*val),
432                ArgumentValue::Double(val) => ArgumentValueJson::Double(*val),
433                ArgumentValue::Pointer(addr) => ArgumentValueJson::Pointer {
434                    address: crate::format_address(*addr),
435                    string_value: None, // TODO: Extract string value if available
436                },
437                ArgumentValue::String(s) => ArgumentValueJson::String(s.clone()),
438                ArgumentValue::Raw(bytes) => ArgumentValueJson::Raw(hex::encode(bytes)),
439                ArgumentValue::Struct {
440                    type_name,
441                    members: _,
442                    size,
443                } => {
444                    // TODO: Full struct serialization
445                    ArgumentValueJson::Object(format!("struct {} (size: {})", type_name, size))
446                }
447                ArgumentValue::Array {
448                    element_type,
449                    elements: _,
450                    count,
451                    element_size,
452                } => {
453                    // TODO: Full array serialization
454                    ArgumentValueJson::Object(format!(
455                        "{}[{}] (element_size: {})",
456                        element_type, count, element_size
457                    ))
458                }
459                ArgumentValue::Union {
460                    type_name,
461                    raw_data: _,
462                    size,
463                } => ArgumentValueJson::Object(format!("union {} (size: {})", type_name, size)),
464                ArgumentValue::Null => ArgumentValueJson::Pointer {
465                    address: crate::string_constants::NULL_ADDRESS.to_string(),
466                    string_value: Some(crate::string_constants::NULL_STRING.to_string()),
467                },
468                ArgumentValue::Unknown {
469                    type_name,
470                    raw_data,
471                    error,
472                } => {
473                    let error_info = error
474                        .as_ref()
475                        .map(|e| format!(": {}", e))
476                        .unwrap_or_default();
477                    ArgumentValueJson::Object(format!(
478                        "unknown {} (size: {}){}",
479                        type_name,
480                        raw_data.len(),
481                        error_info
482                    ))
483                }
484            }
485        } else {
486            ArgumentValueJson::Error(
487                arg.error
488                    .clone()
489                    .unwrap_or_else(|| crate::string_constants::UNKNOWN_ERROR.to_string()),
490            )
491        };
492
493        ArgumentJson {
494            name: arg.name.clone(),
495            type_name: arg.type_name.clone(),
496            value: value_json,
497            location: ArgumentLocationJson {
498                class: format!("{:?}", arg.location.class).to_lowercase(),
499                register_index: arg.location.register_index,
500                stack_offset: arg.location.stack_offset,
501                size: arg.location.size,
502            },
503        }
504    }
505
506    /// Write JSON output to file
507    pub fn write_to_file(&self, trace_session: &TraceSession, file_path: &str) -> Result<()> {
508        let mut file = File::create(file_path)
509            .map_err(|e| CallTraceError::JsonError(serde_json::Error::io(e)))?;
510
511        if self.pretty_print {
512            let json_string = serde_json::to_string_pretty(trace_session)?;
513            file.write_all(json_string.as_bytes())
514                .map_err(|e| CallTraceError::JsonError(serde_json::Error::io(e)))?;
515        } else {
516            serde_json::to_writer(&mut file, trace_session)?;
517        }
518
519        Ok(())
520    }
521
522    /// Generate JSON string
523    pub fn to_string(&self, trace_session: &TraceSession) -> Result<String> {
524        if self.pretty_print {
525            serde_json::to_string_pretty(trace_session).map_err(CallTraceError::JsonError)
526        } else {
527            serde_json::to_string(trace_session).map_err(CallTraceError::JsonError)
528        }
529    }
530}
531
532impl Default for JsonOutputGenerator {
533    fn default() -> Self {
534        Self::new()
535    }
536}
537
538/// Get function name from address using dladdr (currently unused)
539#[allow(dead_code)]
540fn get_function_name_from_address(address: u64) -> Option<String> {
541    use libc::{dladdr, Dl_info};
542    use std::ffi::{c_void, CStr};
543
544    let mut info: Dl_info = unsafe { std::mem::zeroed() };
545    let result = unsafe { dladdr(address as *const c_void, &mut info) };
546
547    if result != 0 && !info.dli_sname.is_null() {
548        unsafe {
549            let name = CStr::from_ptr(info.dli_sname)
550                .to_string_lossy()
551                .into_owned();
552
553            // Try to demangle C++ names
554            Some(demangle_function_name(&name))
555        }
556    } else {
557        None
558    }
559}
560
561/// Simple C++ name demangling
562#[allow(dead_code)]
563fn demangle_function_name(mangled: &str) -> String {
564    // Basic demangling for common patterns
565    if mangled.starts_with("_Z") {
566        // This is a mangled C++ name - in a real implementation,
567        // you'd use a proper demangling library
568        mangled.to_string() // Keep mangled for now
569    } else {
570        mangled.to_string()
571    }
572}
573
574/// Format timestamp as human-readable string
575fn format_timestamp(timestamp_us: u64) -> String {
576    if timestamp_us == 0 {
577        return "0".to_string();
578    }
579
580    // Convert microseconds to seconds and remaining microseconds
581    let seconds = timestamp_us / 1_000_000;
582    let microseconds = timestamp_us % 1_000_000;
583
584    // Format as "seconds.microseconds"
585    if microseconds == 0 {
586        format!("{}", seconds)
587    } else {
588        format!("{}.{:06}", seconds, microseconds)
589    }
590}
591
592/// Collect important environment variables for debugging and analysis
593fn collect_environment_variables() -> std::collections::HashMap<String, String> {
594    let mut env_vars = std::collections::HashMap::new();
595
596    // Important system environment variables
597    let important_vars = [
598        "PATH",
599        "LD_LIBRARY_PATH",
600        "LD_PRELOAD",
601        "USER",
602        "HOME",
603        "SHELL",
604        "LANG",
605        "LC_ALL",
606        "LC_CTYPE",
607        "TERM",
608        "DISPLAY",
609        "PWD",
610        "TMPDIR",
611        "TMP",
612        "TEMP",
613        // CallTrace specific variables
614        "CALLTRACE_OUTPUT",
615        "CALLTRACE_CAPTURE_ARGS",
616        "CALLTRACE_MAX_DEPTH",
617        "CALLTRACE_PRETTY_JSON",
618        "CALLTRACE_DEBUG",
619        "CALLTRACE_FILTER",
620        // Common development variables
621        "CARGO_TARGET_DIR",
622        "RUST_LOG",
623        "RUST_BACKTRACE",
624        "DEBUG",
625        "VERBOSE",
626        // Build and compilation variables
627        "CC",
628        "CXX",
629        "CFLAGS",
630        "CXXFLAGS",
631        "LDFLAGS",
632    ];
633
634    // Collect the specified important variables
635    for var_name in &important_vars {
636        if let Ok(value) = std::env::var(var_name) {
637            env_vars.insert(var_name.to_string(), value);
638        }
639    }
640
641    // Also collect any variables that start with CALLTRACE_ that weren't in the list above
642    for (key, value) in std::env::vars() {
643        if key.starts_with("CALLTRACE_") && !env_vars.contains_key(&key) {
644            env_vars.insert(key, value);
645        }
646    }
647
648    // Limit the number of environment variables to prevent excessive output
649    const MAX_ENV_VARS: usize = 50;
650    if env_vars.len() > MAX_ENV_VARS {
651        // Keep only the first MAX_ENV_VARS items (important ones are added first)
652        let mut limited_vars = std::collections::HashMap::new();
653        for (key, value) in env_vars.into_iter().take(MAX_ENV_VARS) {
654            limited_vars.insert(key, value);
655        }
656        env_vars = limited_vars;
657    }
658
659    env_vars
660}
661
662/// Add hex dependency for raw byte encoding
663#[allow(dead_code)]
664mod hex {
665    pub fn encode(bytes: &[u8]) -> String {
666        bytes.iter().map(|b| format!("{:02x}", b)).collect()
667    }
668}