summaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-09-14 06:43:11 -0400
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-09-14 06:43:11 -0400
commit5dd8165f1754e156a0f9fb16c7a6bfa90e99ed60 (patch)
tree7523e29d8044d4d5b6dcd7c1c888a3491137fcd1 /tools
parent9a7263fce4e3156349a526631805fce66b87fb1b (diff)
downloadopen-iov-5dd8165f1754e156a0f9fb16c7a6bfa90e99ed60.tar.gz
open-iov-5dd8165f1754e156a0f9fb16c7a6bfa90e99ed60.zip
VFIO device registration fix + IOMMUFD callbacks + VFIO ioctl handlers
- Removed IOMMU group addition before vfio_register_emulated_iommu_dev() in nv_vgpu_mdev_create() - Added IOMMUFD callbacks (bind_iommufd, unbind_iommufd, attach_ioas, detach_ioas) using kernel's vfio_iommufd_emulated_* functions - Implemented VFIO ioctl handlers: GET_INFO, GET_REGION_INFO, GET_IRQ_INFO, SET_IRQS, MMAP - Added config space read/write handlers at offset 0x70000000000 - Mmap handler for config space, BAR0 (emulation shadow), BAR1-5 (physical VRAM/registers) - Interrupt handler for VFIO_DEVICE_SET_IRQS (DATA_EVENTFD, DATA_NONE, DATA_BOOL) - Added udev rules for vfio group permissions - Created stack_manager.sh unified tool - Fixed mdev remove crash with deferred cleanup in vfio_device_release callback - Kernel 6.8+ IOMMUFD compatibility - QEMU launches successfully with vGPU passthrough
Diffstat (limited to 'tools')
-rwxr-xr-xtools/benchmark_infrastructure.sh217
-rw-r--r--tools/failure_analyzer.py312
-rw-r--r--tools/gdb_scripts/nvidia_init.gdb179
-rw-r--r--tools/guest_agent/guest_test_agent.py332
-rw-r--r--tools/guest_init_monitor.py207
5 files changed, 1247 insertions, 0 deletions
diff --git a/tools/benchmark_infrastructure.sh b/tools/benchmark_infrastructure.sh
new file mode 100755
index 0000000..606aa10
--- /dev/null
+++ b/tools/benchmark_infrastructure.sh
@@ -0,0 +1,217 @@
+#!/bin/bash
+# benchmark_infrastructure.sh - Benchmark runner for VM vs bare metal comparison
+
+set -euo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+BASE_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
+
+RESULTS_DIR="/tmp/gsp_emu_benchmarks/$(date +%Y%m%d_%H%M%S)"
+mkdir -p "$RESULTS_DIR"
+
+log_info() { echo -e "\033[0;34m[INFO]\033[0m $1"; }
+log_ok() { echo -e "\033[0;32m[OK]\033[0m $1"; }
+log_warn() { echo -e "\033[1;33m[WARN]\033[0m $1"; }
+
+run_bare_metal() {
+ local test_name=$1
+ shift
+ log_info "Running $test_name on bare metal..."
+ local output="$RESULTS_DIR/bare_metal_${test_name}.json"
+ "$@" --benchmark --output "$output" 2>&1 | tee "$RESULTS_DIR/bare_metal_${test_name}.log"
+}
+
+run_in_vm() {
+ local test_name=$1
+ shift
+ log_info "Running $test_name in VM..."
+ local output="$RESULTS_DIR/vm_${test_name}.json"
+ # This would run inside the guest via guest_test_agent
+ # For now, placeholder
+ echo '{"test": "'$test_name'", "status": "not_implemented"}' > "$output"
+}
+
+compare_results() {
+ local test_name=$1
+ local bare_file="$RESULTS_DIR/bare_metal_${test_name}.json"
+ local vm_file="$RESULTS_DIR/vm_${test_name}.json"
+
+ if [[ -f "$bare_file" && -f "$vm_file" ]]; then
+ python3 -c "
+import json, sys
+with open('$bare_file') as f: bare = json.load(f)
+with open('$vm_file') as f: vm = json.load(f)
+print(f'Test: $test_name')
+for key in bare:
+ if key in vm and isinstance(bare[key], (int, float)) and isinstance(vm[key], (int, float)):
+ ratio = vm[key] / bare[key] * 100
+ print(f' {key}: bare={bare[key]:.2f} vm={vm[key]:.2f} ratio={ratio:.1f}%')
+"
+ fi
+}
+
+# CUDA benchmarks
+benchmark_cuda() {
+ log_info "=== CUDA Benchmarks ==="
+
+ # vectorAdd
+ run_bare_metal "vectorAdd" /usr/local/cuda/samples/1_Utilities/vectorAdd/vectorAdd
+ run_in_vm "vectorAdd"
+ compare_results "vectorAdd"
+
+ # matrixMul
+ run_bare_metal "matrixMul" /usr/local/cuda/samples/0_Simple/matrixMul/matrixMul
+ run_in_vm "matrixMul"
+ compare_results "matrixMul"
+
+ # deviceQuery (bandwidth)
+ run_bare_metal "deviceQuery" /usr/local/cuda/samples/1_Utilities/deviceQuery/deviceQuery
+ run_in_vm "deviceQuery"
+ compare_results "deviceQuery"
+
+ # bandwidthTest
+ run_bare_metal "bandwidthTest" /usr/local/cuda/samples/1_Utilities/bandwidthTest/bandwidthTest
+ run_in_vm "bandwidthTest"
+ compare_results "bandwidthTest"
+}
+
+# cuBLAS benchmarks
+benchmark_cublas() {
+ log_info "=== cuBLAS Benchmarks ==="
+ run_bare_metal "cublas_sgemm" /usr/local/cuda/samples/7_CUDALibraries/simpleCUBLAS/simpleCUBLAS
+ run_in_vm "cublas_sgemm"
+ compare_results "cublas_sgemm"
+}
+
+# Graphics benchmarks
+benchmark_graphics() {
+ log_info "=== Graphics Benchmarks ==="
+
+ # vkcube (frame time)
+ run_bare_metal "vkcube" vkcube --duration 10 --benchmark
+ run_in_vm "vkcube"
+ compare_results "vkcube"
+
+ # glmark2
+ run_bare_metal "glmark2" glmark2 --benchmark
+ run_in_vm "glmark2"
+ compare_results "glmark2"
+}
+
+# Video benchmarks
+benchmark_video() {
+ log_info "=== Video Benchmarks ==="
+
+ # NVENC encode
+ run_bare_metal "nvenc_encode" ffmpeg -i test_1080p.yuv -c:v h264_nvenc -f null -
+ run_in_vm "nvenc_encode"
+ compare_results "nvenc_encode"
+
+ # NVDEC decode
+ run_bare_metal "nvdec_decode" ffmpeg -i test_1080p.h264 -c:v h264_nvdec -f null -
+ run_in_vm "nvdec_decode"
+ compare_results "nvdec_decode"
+}
+
+# PyTorch benchmarks
+benchmark_pytorch() {
+ log_info "=== PyTorch Benchmarks ==="
+
+ cat > /tmp/bench_pytorch.py <<'EOF'
+import torch, time, json, sys
+device = 'cuda' if torch.cuda.is_available() else 'cpu'
+results = {}
+
+# Matmul benchmark
+for size in [512, 1024, 2048, 4096]:
+ x = torch.randn(size, size, device=device)
+ y = torch.randn(size, size, device=device)
+ torch.cuda.synchronize() if device == 'cuda' else None
+ start = time.time()
+ for _ in range(10):
+ z = torch.mm(x, y)
+ torch.cuda.synchronize() if device == 'cuda' else None
+ elapsed = (time.time() - start) / 10
+ gflops = 2 * size**3 / elapsed / 1e9
+ results[f'matmul_{size}'] = {'time': elapsed, 'gflops': gflops}
+ print(f"Matmul {size}x{size}: {elapsed:.4f}s ({gflops:.1f} GFLOPS)")
+
+# ResNet-50 forward
+import torchvision.models as models
+model = models.resnet50().to(device)
+model.eval()
+x = torch.randn(32, 3, 224, 224).to(device)
+torch.cuda.synchronize() if device == 'cuda' else None
+start = time.time()
+for _ in range(10):
+ with torch.no_grad():
+ _ = model(x)
+torch.cuda.synchronize() if device == 'cuda' else None
+elapsed = (time.time() - start) / 10
+results['resnet50_forward'] = {'time': elapsed, 'fps': 32/elapsed}
+print(f"ResNet-50 forward: {elapsed:.4f}s ({32/elapsed:.1f} FPS)")
+
+with open(sys.argv[1], 'w') as f:
+ json.dump(results, f, indent=2)
+EOF
+
+ run_bare_metal "pytorch" python3 /tmp/bench_pytorch.py
+ run_in_vm "pytorch"
+ compare_results "pytorch"
+}
+
+# Generate summary report
+generate_summary() {
+ log_info "Generating summary report..."
+ cat > "$RESULTS_DIR/summary.md" <<EOF
+# GSP Emulation Benchmark Summary
+Date: $(date)
+Host: $(hostname)
+Kernel: $(uname -r)
+
+## Results
+
+| Test | Bare Metal | VM | Ratio | Status |
+|------|------------|-----|-------|--------|
+EOF
+
+ for f in "$RESULTS_DIR"/bare_metal_*.json; do
+ test_name=$(basename "$f" .json | sed 's/bare_metal_//')
+ vm_f="$RESULTS_DIR/vm_${test_name}.json"
+ if [[ -f "$vm_f" ]]; then
+ python3 -c "
+import json
+with open('$f') as bf: bare = json.load(bf)
+with open('$vm_f') as vf: vm = json.load(vf)
+for k in bare:
+ if k in vm and isinstance(bare[k], (int, float)):
+ ratio = vm[k] / bare[k] * 100
+ status = '✅' if ratio > 85 else '⚠️' if ratio > 70 else '❌'
+ print(f'| {test_name} / {k} | {bare[k]:.2f} | {vm[k]:.2f} | {ratio:.1f}% | {status} |')
+" >> "$RESULTS_DIR/summary.md"
+ fi
+ done
+
+ log_ok "Summary: $RESULTS_DIR/summary.md"
+}
+
+main() {
+ case "${1:-all}" in
+ cuda) benchmark_cuda ;;
+ cublas) benchmark_cublas ;;
+ graphics) benchmark_graphics ;;
+ video) benchmark_video ;;
+ pytorch) benchmark_pytorch ;;
+ all)
+ benchmark_cuda
+ benchmark_cublas
+ benchmark_graphics
+ benchmark_video
+ benchmark_pytorch
+ ;;
+ *) echo "Usage: $0 {cuda|cublas|graphics|video|pytorch|all}"; exit 1 ;;
+ esac
+ generate_summary
+}
+
+main "$@" \ No newline at end of file
diff --git a/tools/failure_analyzer.py b/tools/failure_analyzer.py
new file mode 100644
index 0000000..7ffb776
--- /dev/null
+++ b/tools/failure_analyzer.py
@@ -0,0 +1,312 @@
+#!/usr/bin/env python3
+"""
+failure_analyzer.py - Analyze and classify guest initialization failures
+Correlates host tracepoints, guest logs, and QEMU state
+"""
+
+import sys
+import os
+import re
+import json
+import argparse
+from datetime import datetime
+from pathlib import Path
+from collections import defaultdict
+from typing import Dict, List, Optional, Any
+
+class FailureAnalyzer:
+ def __init__(self):
+ self.failure_signatures = self._load_signatures()
+ self.recommendations = self._load_recommendations()
+
+ def _load_signatures(self) -> Dict:
+ return {
+ "PCI_PROBE_FAILURE": {
+ "guest_patterns": [
+ r"nvidia: probe of .* failed with error -?\d+",
+ r"pci .*: can't claim BAR \d+",
+ r"pci .*: BAR \d+: no space for \[mem size",
+ r"pci .*: BAR \d+: failed to assign",
+ ],
+ "host_trace_patterns": [
+ r"gsp_emu_rpc_received.*rpc_type=0x1.*status=-?\d+",
+ r"gsp_emu_fallback_used.*rpc_type=0x1",
+ ],
+ "severity": "critical",
+ "category": "pci_config"
+ },
+ "GSP_FIRMWARE_LOAD_FAILURE": {
+ "guest_patterns": [
+ r"GSP.*firmware.*load.*failed",
+ r"GSP.*bootstrap.*failed",
+ r"nv_gsp_firmware_load.*error",
+ r"GSP.*RPC.*timeout",
+ r"GSP.*communication.*failed",
+ ],
+ "host_trace_patterns": [
+ r"gsp_emu_firmware_analysis.*matched=0",
+ r"gsp_emu_dispatcher_select.*emulation=1",
+ r"gsp_emu_rpc_received.*rpc_type=0x1.*seq_id=1",
+ r"gsp_emu_rpc_processed.*status=-110",
+ ],
+ "severity": "critical",
+ "category": "gsp_firmware"
+ },
+ "MSIX_SETUP_FAILURE": {
+ "guest_patterns": [
+ r"MSI-X.*failed",
+ r"pci_enable_msix.*failed",
+ r"request_threaded_irq.*failed",
+ r"irq.*allocation.*failed",
+ ],
+ "host_trace_patterns": [
+ r"gsp_emu_msix_injected.*vector=0",
+ r"gsp_emu_rpc_processed.*rpc_type=0x7.*status=-?\d+",
+ ],
+ "severity": "high",
+ "category": "interrupts"
+ },
+ "BAR_MAPPING_FAILURE": {
+ "guest_patterns": [
+ r"ioremap.*failed",
+ r"pci_iomap.*failed",
+ r"BAR.*mapping.*failed",
+ r"VFIO.*region.*mmap.*failed",
+ ],
+ "host_trace_patterns": [
+ r"gsp_emu_shared_mem_access.*is_write=1",
+ r"gsp_emu_bar_emulation.*",
+ ],
+ "severity": "high",
+ "category": "memory_mapping"
+ },
+ "DMA_MAPPING_FAILURE": {
+ "guest_patterns": [
+ r"dma_map.*failed",
+ r"IOMMU.*fault",
+ r"DMAR.*error",
+ r"iommu.*domain.*failed",
+ ],
+ "host_trace_patterns": [
+ r"gsp_emu_rpc_processed.*rpc_type=0x5.*status=-?\d+",
+ r"gsp_emu_fallback_used.*rpc_type=0x5",
+ ],
+ "severity": "high",
+ "category": "dma"
+ },
+ "VGPU_VFIO_FAILURE": {
+ "guest_patterns": [
+ r"vGPU VFIO.*failed",
+ r"nvidia_vgpu_vfio_get_ops.*failed",
+ r"mdev.*register.*failed",
+ r"VFIO.*device.*not found",
+ ],
+ "host_trace_patterns": [
+ r"gsp_emu_thread_state.*state=0",
+ ],
+ "severity": "critical",
+ "category": "vfio"
+ },
+ "GSP_RPC_TIMEOUT": {
+ "guest_patterns": [
+ r"GSP.*RPC.*timeout",
+ r"doorbell.*timeout",
+ r"GSP.*response.*timeout",
+ ],
+ "host_trace_patterns": [
+ r"gsp_emu_doorbell_poll.*",
+ r"gsp_emu_rpc_received.*",
+ r"gsp_emu_rpc_processed.*latency_ns=\d{9,}",
+ ],
+ "severity": "medium",
+ "category": "rpc_timeout"
+ },
+ }
+
+ def _load_recommendations(self) -> Dict:
+ return {
+ "PCI_PROBE_FAILURE": [
+ "Check PCI config space emulation in open-iov-vfio.ko (main.c)",
+ "Verify BAR sizes match physical GPU (16MB/256MB/32MB/32MB)",
+ "Check MSI-X capability pointer (0x70) and table/PBA offsets",
+ "Ensure mdev parent registered with NVIDIA host driver",
+ ],
+ "GSP_FIRMWARE_LOAD_FAILURE": [
+ "Verify firmware file exists at /lib/firmware/nvidia/tu102/gsp/",
+ "Check firmware CRC32/SHA256 matches known signature",
+ "Ensure GSP emulation thread started (gsp_emu_thread_state)",
+ "Check doorbell register offset (BAR0+0x110C00)",
+ "Verify shared memory mapping (BAR0+0x1000, 4KB)",
+ ],
+ "MSIX_SETUP_FAILURE": [
+ "Verify MSI-X table at BAR0+0x2000 (32 entries × 16 bytes)",
+ "Verify MSI-X PBA at BAR0+0x3000 (64-bit)",
+ "Check eventfd signaling for interrupt injection",
+ "Ensure guest MSI-X enable bit set in Message Control",
+ ],
+ "BAR_MAPPING_FAILURE": [
+ "Check VFIO region mmap handlers (bar_emulation.c)",
+ "Verify BAR0 includes doorbell (0x110C00) and shared mem (0x1000)",
+ "Check MSI-X region (VFIO_PCI_MSIX_REGION_INDEX)",
+ "Ensure WC (write-combine) mapping for BAR0",
+ ],
+ "DMA_MAPPING_FAILURE": [
+ "Verify IOMMU domain attached to VFIO group",
+ "Check DMA mapping in memory.c (nv_vgpu_dma_map)",
+ "Ensure physical pages pinned before mapping",
+ "Check for IOMMU faults in dmesg",
+ ],
+ "VGPU_VFIO_FAILURE": [
+ "Verify nvidia.ko loaded before open-iov-vfio.ko",
+ "Check KBUILD_EXTRA_SYMBOLS points to nvidia.ko Module.symvers",
+ "Verify nvidia_vgpu_vfio_get_ops exported by nvidia.ko",
+ "Check mdev parent registration callback",
+ ],
+ "GSP_RPC_TIMEOUT": [
+ "Check GSP emulation thread CPU usage (should be polling)",
+ "Verify doorbell polling adaptive (busy-wait 100us then usleep)",
+ "Check shared memory coherence (cache flush/invalidate)",
+ "Trace RPC sequence IDs for gaps/duplicates",
+ ],
+ }
+
+ def analyze(self, guest_log_file: str, host_trace_file: str, qemu_log_file: str = None) -> Dict:
+ """Analyze failure from log files"""
+
+ guest_logs = self._read_logs(guest_log_file)
+ host_traces = self._read_logs(host_trace_file)
+ qemu_logs = self._read_logs(qemu_log_file) if qemu_log_file else []
+
+ # Classify failure
+ failure_type = self._classify_failure(guest_logs, host_traces)
+
+ # Get matching evidence
+ evidence = self._collect_evidence(failure_type, guest_logs, host_traces)
+
+ # Generate report
+ report = {
+ "timestamp": datetime.now().isoformat(),
+ "failure_type": failure_type,
+ "severity": self.failure_signatures.get(failure_type, {}).get("severity", "unknown"),
+ "category": self.failure_signatures.get(failure_type, {}).get("category", "unknown"),
+ "evidence": evidence,
+ "recommendations": self.recommendations.get(failure_type, []),
+ "guest_log_summary": self._summarize_logs(guest_logs),
+ "host_trace_summary": self._summarize_logs(host_traces),
+ }
+
+ return report
+
+ def _read_logs(self, filepath: str) -> List[str]:
+ if not filepath or not os.path.exists(filepath):
+ return []
+ with open(filepath, 'r') as f:
+ return [line.strip() for line in f.readlines()]
+
+ def _classify_failure(self, guest_logs: List[str], host_traces: List[str]) -> str:
+ """Classify failure based on patterns"""
+ scores = defaultdict(int)
+
+ for failure_type, sig in self.failure_signatures.items():
+ # Check guest patterns
+ for pattern in sig.get("guest_patterns", []):
+ for line in guest_logs:
+ if re.search(pattern, line, re.IGNORECASE):
+ scores[failure_type] += 3
+
+ # Check host trace patterns
+ for pattern in sig.get("host_trace_patterns", []):
+ for line in host_traces:
+ if re.search(pattern, line):
+ scores[failure_type] += 2
+
+ if not scores:
+ return "UNKNOWN"
+
+ # Return highest scoring failure type
+ return max(scores, key=scores.get)
+
+ def _collect_evidence(self, failure_type: str, guest_logs: List[str], host_traces: List[str]) -> Dict:
+ """Collect matching evidence for failure type"""
+ sig = self.failure_signatures.get(failure_type, {})
+ evidence = {"guest": [], "host": []}
+
+ for pattern in sig.get("guest_patterns", []):
+ for line in guest_logs:
+ if re.search(pattern, line, re.IGNORECASE):
+ evidence["guest"].append({"pattern": pattern, "match": line})
+
+ for pattern in sig.get("host_trace_patterns", []):
+ for line in host_traces:
+ if re.search(pattern, line):
+ evidence["host"].append({"pattern": pattern, "match": line})
+
+ return evidence
+
+ def _summarize_logs(self, logs: List[str]) -> Dict:
+ """Summarize log content"""
+ return {
+ "total_lines": len(logs),
+ "error_lines": sum(1 for l in logs if "error" in l.lower() or "fail" in l.lower()),
+ "nvidia_lines": sum(1 for l in logs if "nvidia" in l.lower()),
+ "gsp_lines": sum(1 for l in logs if "gsp" in l.lower()),
+ "last_20": logs[-20:] if logs else [],
+ }
+
+ def print_report(self, report: Dict):
+ """Print formatted failure report"""
+ print("\n" + "="*70)
+ print(f"FAILURE ANALYSIS REPORT")
+ print("="*70)
+ print(f"Timestamp: {report['timestamp']}")
+ print(f"Failure Type: {report['failure_type']}")
+ print(f"Severity: {report['severity']}")
+ print(f"Category: {report['category']}")
+ print()
+
+ print("EVIDENCE:")
+ print("-"*70)
+ for src in ["guest", "host"]:
+ ev = report['evidence'].get(src, [])
+ if ev:
+ print(f" {src.upper()} ({len(ev)} matches):")
+ for e in ev[:5]:
+ print(f" Pattern: {e['pattern']}")
+ print(f" Match: {e['match'][:120]}")
+ print()
+
+ print("RECOMMENDATIONS:")
+ print("-"*70)
+ for i, rec in enumerate(report['recommendations'], 1):
+ print(f" {i}. {rec}")
+ print()
+
+ print("LOG SUMMARIES:")
+ print("-"*70)
+ for src in ["guest", "host"]:
+ summ = report.get(f'{src}_log_summary', {})
+ print(f" {src.upper()}: {summ.get('total_lines', 0)} lines, "
+ f"{summ.get('error_lines', 0)} errors, "
+ f"{summ.get('nvidia_lines', 0)} nvidia, "
+ f"{summ.get('gsp_lines', 0)} gsp")
+ print("="*70)
+
+def main():
+ parser = argparse.ArgumentParser(description="Analyze guest NVIDIA driver initialization failures")
+ parser.add_argument("--guest-log", required=True, help="Guest serial log file")
+ parser.add_argument("--host-trace", required=True, help="Host trace_pipe log file")
+ parser.add_argument("--qemu-log", help="QEMU log file (optional)")
+ parser.add_argument("--output", help="Output JSON report file")
+ args = parser.parse_args()
+
+ analyzer = FailureAnalyzer()
+ report = analyzer.analyze(args.guest_log, args.host_trace, args.qemu_log)
+ analyzer.print_report(report)
+
+ if args.output:
+ with open(args.output, 'w') as f:
+ json.dump(report, f, indent=2)
+ print(f"\nReport saved to: {args.output}")
+
+if __name__ == "__main__":
+ main() \ No newline at end of file
diff --git a/tools/gdb_scripts/nvidia_init.gdb b/tools/gdb_scripts/nvidia_init.gdb
new file mode 100644
index 0000000..c217419
--- /dev/null
+++ b/tools/gdb_scripts/nvidia_init.gdb
@@ -0,0 +1,179 @@
+# nvidia_init.gdb - GDB script for debugging NVIDIA driver initialization in guest
+# Usage: gdb -x nvidia_init.gdb ./vmlinux
+# Then: (gdb) target remote localhost:1234
+
+# ============================================================
+# CONFIGURATION
+# ============================================================
+# Path to guest kernel vmlinux with debug symbols
+# set pagination off
+# set confirm off
+
+# ============================================================
+# BREAKPOINTS - NVIDIA Driver Probe/Init
+# ============================================================
+
+# Core probe entry points
+break nvidia_probe
+break nvidia_init_module
+break nvidia_exit_module
+
+# GSP/RM initialization
+break nv_init_gsp
+break nv_gsp_init
+break nv_gsp_firmware_load
+break nv_gsp_bootstrap
+break nv_gsp_rpc_init
+
+# VFIO/mdev integration
+break nvidia_vgpu_vfio_get_ops
+break nvidia_vgpu_vfio_set_ops
+break nv_vgpu_mdev_register_parent
+break nv_vgpu_mdev_unregister_parent
+break nv_vgpu_create_callback
+break nv_vgpu_delete_callback
+break nv_vgpu_bar_info_callback
+break nv_vgpu_start_callback
+
+# PCI config space
+break nvidia_pci_probe
+break nvidia_pci_remove
+break pci_enable_device
+break pci_request_regions
+
+# MSI/MSI-X
+break pci_enable_msi_block
+break pci_enable_msix_range
+break nvidia_setup_msi
+break nvidia_setup_msix
+
+# BAR mapping
+break pci_iomap
+break pci_iounmap
+break nvidia_map_bars
+
+# Interrupt handling
+break request_threaded_irq
+break free_irq
+break nvidia_isr
+break nvidia_msi_isr
+break nvidia_msix_isr
+
+# DMA mapping
+break dma_map_page
+break dma_unmap_page
+break dma_map_sg
+break dma_unmap_sg
+
+# GSP RPC communication (OpenRM internal)
+break nv_gsp_rpc_send
+break nv_gsp_rpc_recv
+break nv_gsp_doorbell_ring
+break nv_gsp_doorbell_poll
+
+# ============================================================
+# WATCHPOINTS - Key Data Structures
+# ============================================================
+
+# Watch for GSP firmware load completion
+# watch *(int*)nv_gsp_fw_loaded
+
+# Watch for MSI-X table setup
+# watch *(int*)nv_msix_table_configured
+
+# ============================================================
+# COMMANDS - Helper Functions
+# ============================================================
+
+define nvidia_backtrace
+ bt
+ printf "\n=== NVIDIA Driver Stack ===\n"
+ info registers
+end
+document nvidia_backtrace
+ Show backtrace with register state for NVIDIA driver
+end
+
+define nvidia_pci_state
+ printf "=== PCI Config Space ===\n"
+ # Requires access to pci_dev structure
+ # p/x ((struct pci_dev*)$rdi)->vendor
+ # p/x ((struct pci_dev*)$rdi)->device
+ # p/x ((struct pci_dev*)$rdi)->command
+ # p/x ((struct pci_dev*)$rdi)->status
+end
+document nvidia_pci_state
+ Print PCI device state
+end
+
+define nvidia_gsp_state
+ printf "=== GSP State ===\n"
+ # p/x nv_gsp_fw_version
+ # p/x nv_gsp_fw_loaded
+ # p/x nv_gsp_rpc_initialized
+end
+document nvidia_gsp_state
+ Print GSP firmware state
+end
+
+define nvidia_vfio_state
+ printf "=== VFIO/mdev State ===\n"
+ # p/x nv_vgpu_mdev_registered
+ # p/x nv_vgpu_vfio_ops
+end
+document nvidia_vfio_state
+ Print VFIO/mdev registration state
+end
+
+# ============================================================
+# TRACEPOINTS (if kernel has tracepoints enabled)
+# ============================================================
+
+# trace nvidia:nvidia_probe_entry
+# trace nvidia:nvidia_probe_exit
+# trace nvidia:gsp_firmware_load
+# trace nvidia:gsp_rpc_send
+# trace nvidia:gsp_rpc_recv
+
+# ============================================================
+# AUTOMATION - Continue and Log
+# ============================================================
+
+# Log all breakpoint hits to file
+set logging file nvidia_init_debug.log
+set logging on
+
+# Auto-continue after logging (uncomment for automated runs)
+# set confirm off
+# commands 1
+# nvidia_backtrace
+# continue
+# end
+
+# ============================================================
+# CONVENIENCE ALIASES
+# ============================================================
+
+define hook-stop
+ # Print current function on every stop
+ printf "\n=== STOPPED at %s ===\n", __func__
+ # Print key state
+ # nvidia_gsp_state
+end
+
+# ============================================================
+# USAGE NOTES
+# ============================================================
+# 1. Compile guest kernel with CONFIG_DEBUG_INFO=y CONFIG_GDB_SCRIPTS=y
+# 2. Copy vmlinux from guest to host
+# 3. Launch QEMU: ./scripts/qemu_gdb_attach.sh
+# 4. In another terminal: gdb -x tools/gdb_scripts/nvidia_init.gdb ./vmlinux
+# 5. (gdb) target remote localhost:1234
+# 6. (gdb) continue
+#
+# For specific debugging:
+# - PCI probe issues: break nvidia_probe
+# - GSP firmware load: break nv_gsp_firmware_load
+# - MSI-X setup: break pci_enable_msix_range
+# - BAR mapping: break pci_iomap
+# - RPC communication: break nv_gsp_rpc_send \ No newline at end of file
diff --git a/tools/guest_agent/guest_test_agent.py b/tools/guest_agent/guest_test_agent.py
new file mode 100644
index 0000000..15a578d
--- /dev/null
+++ b/tools/guest_agent/guest_test_agent.py
@@ -0,0 +1,332 @@
+#!/usr/bin/env python3
+"""
+guest_test_agent.py - Runs inside guest VM, executes tests, reports results
+Communication via vsock, serial, or HTTP
+"""
+
+import sys
+import os
+import json
+import time
+import subprocess
+import threading
+import socket
+import argparse
+from datetime import datetime
+from pathlib import Path
+
+class GuestTestAgent:
+ def __init__(self, listen_port=9999, report_url=None, vsock_cid=3, vsock_port=9999):
+ self.listen_port = listen_port
+ self.report_url = report_url
+ self.vsock_cid = vsock_cid
+ self.vsock_port = vsock_port
+ self.running = False
+ self.results = []
+
+ def start_server(self):
+ """Start command listener (TCP or vsock)"""
+ self.running = True
+
+ # Try vsock first (preferred for VM-host communication)
+ try:
+ import socket
+ if hasattr(socket, 'AF_VSOCK'):
+ self.server = socket.socket(socket.AF_VSOCK, socket.SOCK_STREAM)
+ self.server.bind((self.vsock_cid, self.vsock_port))
+ self.server.listen(5)
+ print(f"[{datetime.now()}] Guest agent listening on vsock CID:{self.vsock_cid} PORT:{self.vsock_port}")
+ else:
+ raise ImportError("vsock not available")
+ except Exception:
+ # Fallback to TCP
+ self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ self.server.bind(('0.0.0.0', self.listen_port))
+ self.server.listen(5)
+ print(f"[{datetime.now()}] Guest agent listening on TCP port {self.listen_port}")
+
+ while self.running:
+ try:
+ conn, addr = self.server.accept()
+ threading.Thread(target=self._handle_connection, args=(conn,), daemon=True).start()
+ except Exception as e:
+ if self.running:
+ print(f"[{datetime.now()}] Server error: {e}")
+
+ def _handle_connection(self, conn):
+ """Handle incoming test commands"""
+ try:
+ data = conn.recv(4096).decode('utf-8')
+ if not data:
+ return
+
+ cmd = json.loads(data)
+ action = cmd.get('action')
+ test_id = cmd.get('test_id', 'unknown')
+
+ print(f"[{datetime.now()}] Received command: {action} (test_id: {test_id})")
+
+ if action == 'ping':
+ result = {'status': 'ok', 'agent': 'guest_test_agent', 'version': '1.0'}
+ elif action == 'run_test':
+ result = self._run_test(cmd.get('test'), cmd.get('args', {}))
+ elif action == 'run_suite':
+ result = self._run_suite(cmd.get('suite'))
+ elif action == 'get_results':
+ result = {'results': self.results}
+ elif action == 'shutdown':
+ self.running = False
+ result = {'status': 'shutting_down'}
+ else:
+ result = {'error': f'Unknown action: {action}'}
+
+ result['test_id'] = test_id
+ conn.send(json.dumps(result).encode('utf-8'))
+
+ except Exception as e:
+ conn.send(json.dumps({'error': str(e)}).encode('utf-8'))
+ finally:
+ conn.close()
+
+ def _run_test(self, test_name, args):
+ """Run a single test"""
+ test_method = getattr(self, f'test_{test_name}', None)
+ if not test_method:
+ return {'status': 'error', 'message': f'Unknown test: {test_name}'}
+
+ start = time.time()
+ try:
+ result = test_method(**args)
+ result['duration'] = time.time() - start
+ result['timestamp'] = datetime.now().isoformat()
+ self.results.append(result)
+ return result
+ except Exception as e:
+ return {'status': 'error', 'message': str(e), 'duration': time.time() - start}
+
+ def _run_suite(self, suite_name):
+ """Run a test suite"""
+ suite_method = getattr(self, f'suite_{suite_name}', None)
+ if not suite_method:
+ return {'status': 'error', 'message': f'Unknown suite: {suite_name}'}
+
+ return suite_method()
+
+ # ============================================================
+ # INDIVIDUAL TESTS
+ # ============================================================
+
+ def test_nvidia_smi(self):
+ """Run nvidia-smi and verify GPU detection"""
+ try:
+ out = subprocess.check_output(['nvidia-smi'], text=True, timeout=30)
+ return {
+ 'test': 'nvidia_smi',
+ 'status': 'pass' if 'NVIDIA' in out else 'fail',
+ 'output': out[:2000]
+ }
+ except subprocess.CalledProcessError as e:
+ return {'test': 'nvidia_smi', 'status': 'fail', 'error': str(e), 'output': e.output}
+ except Exception as e:
+ return {'test': 'nvidia_smi', 'status': 'error', 'message': str(e)}
+
+ def test_cuda_vectoradd(self):
+ """Compile and run CUDA vectorAdd sample"""
+ try:
+ # Find CUDA samples
+ cuda_samples = Path('/usr/local/cuda/samples')
+ if not cuda_samples.exists():
+ cuda_samples = Path('/opt/cuda/samples')
+
+ vecadd_dir = cuda_samples / '1_Utilities' / 'vectorAdd'
+ if not vecadd_dir.exists():
+ return {'test': 'cuda_vectoradd', 'status': 'skip', 'message': 'vectorAdd sample not found'}
+
+ # Build
+ subprocess.check_call(['make'], cwd=vecadd_dir, timeout=60)
+
+ # Run
+ out = subprocess.check_output(['./vectorAdd'], cwd=vecadd_dir, text=True, timeout=30)
+
+ return {
+ 'test': 'cuda_vectoradd',
+ 'status': 'pass' if 'Test passed' in out else 'fail',
+ 'output': out[:2000]
+ }
+ except subprocess.CalledProcessError as e:
+ return {'test': 'cuda_vectoradd', 'status': 'fail', 'error': str(e), 'output': e.output}
+ except Exception as e:
+ return {'test': 'cuda_vectoradd', 'status': 'error', 'message': str(e)}
+
+ def test_cuda_devquery(self):
+ """Run CUDA deviceQuery"""
+ try:
+ cuda_samples = Path('/usr/local/cuda/samples')
+ if not cuda_samples.exists():
+ cuda_samples = Path('/opt/cuda/samples')
+
+ devq_dir = cuda_samples / '1_Utilities' / 'deviceQuery'
+ if not devq_dir.exists():
+ return {'test': 'cuda_devquery', 'status': 'skip', 'message': 'deviceQuery sample not found'}
+
+ subprocess.check_call(['make'], cwd=devq_dir, timeout=60)
+ out = subprocess.check_output(['./deviceQuery'], cwd=devq_dir, text=True, timeout=30)
+
+ return {
+ 'test': 'cuda_devquery',
+ 'status': 'pass' if 'Result = PASS' in out else 'fail',
+ 'output': out[:3000]
+ }
+ except Exception as e:
+ return {'test': 'cuda_devquery', 'status': 'error', 'message': str(e)}
+
+ def test_vulkan_info(self):
+ """Run vulkaninfo"""
+ try:
+ out = subprocess.check_output(['vulkaninfo'], text=True, timeout=30)
+ return {
+ 'test': 'vulkan_info',
+ 'status': 'pass' if 'GPU id' in out else 'fail',
+ 'output': out[:3000]
+ }
+ except Exception as e:
+ return {'test': 'vulkan_info', 'status': 'error', 'message': str(e)}
+
+ def test_vkcube(self):
+ """Run vkcube (headless)"""
+ try:
+ # vkcube may need display; try with xvfb or skip
+ out = subprocess.check_output(['vkcube', '--duration', '2'], text=True, timeout=30)
+ return {'test': 'vkcube', 'status': 'pass', 'output': out[:2000]}
+ except FileNotFoundError:
+ return {'test': 'vkcube', 'status': 'skip', 'message': 'vkcube not installed'}
+ except Exception as e:
+ return {'test': 'vkcube', 'status': 'error', 'message': str(e)}
+
+ def test_glxgears(self):
+ """Run glxgears (brief)"""
+ try:
+ out = subprocess.check_output(['timeout', '5', 'glxgears'], text=True, timeout=15)
+ return {'test': 'glxgears', 'status': 'pass', 'output': out[:1000]}
+ except Exception as e:
+ return {'test': 'glxgears', 'status': 'error', 'message': str(e)}
+
+ def test_ffmpeg_nvenc(self):
+ """Test FFmpeg NVENC"""
+ try:
+ out = subprocess.check_output(['ffmpeg', '-encoders'], text=True, timeout=10)
+ has_nvenc = 'h264_nvenc' in out or 'hevc_nvenc' in out
+ return {
+ 'test': 'ffmpeg_nvenc',
+ 'status': 'pass' if has_nvenc else 'fail',
+ 'output': out[:2000]
+ }
+ except Exception as e:
+ return {'test': 'ffmpeg_nvenc', 'status': 'error', 'message': str(e)}
+
+ def test_pytorch(self):
+ """Test PyTorch CUDA"""
+ try:
+ code = """
+import torch
+print(f'CUDA available: {torch.cuda.is_available()}')
+if torch.cuda.is_available():
+ print(f'Device count: {torch.cuda.device_count()}')
+ print(f'Device name: {torch.cuda.get_device_name(0)}')
+ x = torch.randn(100, 100).cuda()
+ y = torch.randn(100, 100).cuda()
+ z = torch.mm(x, y)
+ print(f'Matmul result shape: {z.shape}')
+ print('PyTorch CUDA test: PASS')
+else:
+ print('PyTorch CUDA test: FAIL - CUDA not available')
+"""
+ out = subprocess.check_output([sys.executable, '-c', code], text=True, timeout=60)
+ return {
+ 'test': 'pytorch',
+ 'status': 'pass' if 'PASS' in out else 'fail',
+ 'output': out
+ }
+ except Exception as e:
+ return {'test': 'pytorch', 'status': 'error', 'message': str(e)}
+
+ # ============================================================
+ # TEST SUITES
+ # ============================================================
+
+ def suite_smoke(self):
+ """Quick smoke test suite"""
+ tests = ['nvidia_smi', 'cuda_devquery', 'cuda_vectoradd']
+ return self._run_test_list(tests)
+
+ def suite_compute(self):
+ """Compute-focused test suite"""
+ tests = ['nvidia_smi', 'cuda_devquery', 'cuda_vectoradd', 'pytorch']
+ return self._run_test_list(tests)
+
+ def suite_graphics(self):
+ """Graphics-focused test suite"""
+ tests = ['nvidia_smi', 'vulkan_info', 'vkcube', 'glxgears']
+ return self._run_test_list(tests)
+
+ def suite_video(self):
+ """Video encode/decode test suite"""
+ tests = ['nvidia_smi', 'ffmpeg_nvenc']
+ return self._run_test_list(tests)
+
+ def suite_full(self):
+ """Full test suite"""
+ tests = ['nvidia_smi', 'cuda_devquery', 'cuda_vectoradd', 'vulkan_info', 'vkcube', 'glxgears', 'ffmpeg_nvenc', 'pytorch']
+ return self._run_test_list(tests)
+
+ def _run_test_list(self, tests):
+ """Run a list of tests"""
+ results = []
+ for test in tests:
+ print(f"[{datetime.now()}] Running suite test: {test}")
+ result = self._run_test(test, {})
+ results.append(result)
+ print(f" -> {result.get('status', 'unknown')}")
+
+ passed = sum(1 for r in results if r.get('status') == 'pass')
+ failed = sum(1 for r in results if r.get('status') in ('fail', 'error'))
+ skipped = sum(1 for r in results if r.get('status') == 'skip')
+
+ return {
+ 'suite': 'custom',
+ 'total': len(tests),
+ 'passed': passed,
+ 'failed': failed,
+ 'skipped': skipped,
+ 'results': results
+ }
+
+def main():
+ parser = argparse.ArgumentParser(description="Guest Test Agent")
+ parser.add_argument('--port', type=int, default=9999, help='TCP listen port')
+ parser.add_argument('--vsock-cid', type=int, default=3, help='VSOCK CID')
+ parser.add_argument('--vsock-port', type=int, default=9999, help='VSOCK port')
+ parser.add_argument('--run', help='Run single test and exit (for standalone mode)')
+ parser.add_argument('--suite', help='Run test suite and exit')
+ args = parser.parse_args()
+
+ agent = GuestTestAgent(listen_port=args.port, vsock_cid=args.vsock_cid, vsock_port=args.vsock_port)
+
+ if args.run:
+ # Standalone mode
+ result = agent._run_test(args.run, {})
+ print(json.dumps(result, indent=2))
+ elif args.suite:
+ result = getattr(agent, f'suite_{args.suite}')()
+ print(json.dumps(result, indent=2))
+ else:
+ # Server mode
+ try:
+ agent.start_server()
+ except KeyboardInterrupt:
+ print("\nShutting down...")
+ agent.running = False
+
+if __name__ == "__main__":
+ main() \ No newline at end of file
diff --git a/tools/guest_init_monitor.py b/tools/guest_init_monitor.py
new file mode 100644
index 0000000..bc34a08
--- /dev/null
+++ b/tools/guest_init_monitor.py
@@ -0,0 +1,207 @@
+#!/usr/bin/env python3
+"""
+guest_init_monitor.py - Monitor guest kernel initialization for NVIDIA driver
+Correlates host tracepoints with guest dmesg/serial output
+"""
+
+import sys
+import os
+import re
+import time
+import subprocess
+import threading
+import json
+from datetime import datetime
+from collections import deque
+from pathlib import Path
+
+class GuestInitMonitor:
+ def __init__(self, guest_serial="/dev/ttyS0", host_trace_pipe="/sys/kernel/debug/tracing/trace_pipe"):
+ self.guest_serial = guest_serial
+ self.host_trace_pipe = host_trace_pipe
+ self.guest_log = deque(maxlen=1000)
+ self.host_events = deque(maxlen=1000)
+ self.failure_patterns = self._load_failure_patterns()
+ self.running = False
+
+ def _load_failure_patterns(self):
+ return {
+ "pci_probe_fail": [
+ r"nvidia: probe of .* failed with error -?\d+",
+ r"pci .*: can't claim BAR \d+",
+ r"pci .*: BAR \d+: no space for",
+ ],
+ "gsp_firmware_fail": [
+ r"GSP.*firmware.*load.*failed",
+ r"GSP.*bootstrap.*failed",
+ r"GSP.*RPC.*timeout",
+ r"nv_gsp_firmware_load.*error",
+ ],
+ "msix_fail": [
+ r"MSI-X.*failed",
+ r"pci_enable_msix.*failed",
+ r"request_threaded_irq.*failed",
+ ],
+ "bar_map_fail": [
+ r"ioremap.*failed",
+ r"pci_iomap.*failed",
+ r"BAR.*mapping.*failed",
+ ],
+ "dma_fail": [
+ r"dma_map.*failed",
+ r"IOMMU.*fault",
+ r"DMAR.*error",
+ ],
+ "vgpu_vfio_fail": [
+ r"vGPU VFIO.*failed",
+ r"nvidia_vgpu_vfio_get_ops.*failed",
+ r"mdev.*register.*failed",
+ ],
+ }
+
+ def start(self):
+ self.running = True
+ self.guest_thread = threading.Thread(target=self._monitor_guest_serial, daemon=True)
+ self.host_thread = threading.Thread(target=self._monitor_host_traces, daemon=True)
+ self.guest_thread.start()
+ self.host_thread.start()
+ print(f"[{datetime.now()}] Guest init monitor started")
+ print(f" Guest serial: {self.guest_serial}")
+ print(f" Host trace pipe: {self.host_trace_pipe}")
+
+ def stop(self):
+ self.running = False
+ if hasattr(self, 'guest_thread'):
+ self.guest_thread.join(timeout=2)
+ if hasattr(self, 'host_thread'):
+ self.host_thread.join(timeout=2)
+ print(f"[{datetime.now()}] Guest init monitor stopped")
+
+ def _monitor_guest_serial(self):
+ """Monitor guest kernel log via serial console"""
+ try:
+ # Try to open serial device or use virsh console
+ proc = subprocess.Popen(
+ ["timeout", "300", "cat", self.guest_serial],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ text=True
+ )
+ for line in proc.stdout:
+ if not self.running:
+ break
+ self._process_guest_line(line.strip())
+ except Exception as e:
+ print(f"[{datetime.now()}] Guest serial monitor error: {e}")
+
+ def _monitor_host_traces(self):
+ """Monitor host kernel tracepoints"""
+ try:
+ proc = subprocess.Popen(
+ ["cat", self.host_trace_pipe],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ text=True
+ )
+ for line in proc.stdout:
+ if not self.running:
+ break
+ self._process_host_trace(line.strip())
+ except Exception as e:
+ print(f"[{datetime.now()}] Host trace monitor error: {e}")
+
+ def _process_guest_line(self, line):
+ """Process a line from guest serial console"""
+ timestamp = datetime.now().isoformat()
+ entry = {"timestamp": timestamp, "source": "guest", "line": line}
+ self.guest_log.append(entry)
+
+ # Check for failure patterns
+ failure = self._classify_failure(line)
+ if failure:
+ self._report_failure(failure, line, "guest")
+
+ # Print important lines
+ if any(kw in line.lower() for kw in ["nvidia", "gsp", "vfio", "mdev", "error", "fail", "timeout"]):
+ print(f"[GUEST] {line}")
+
+ def _process_host_trace(self, line):
+ """Process a line from host trace_pipe"""
+ timestamp = datetime.now().isoformat()
+ entry = {"timestamp": timestamp, "source": "host_trace", "line": line}
+ self.host_events.append(entry)
+
+ # Look for GSP emulation tracepoints
+ if "gsp_emu" in line:
+ print(f"[HOST TRACE] {line}")
+ # Correlate with recent guest logs
+ self._correlate_events(line)
+
+ def _classify_failure(self, line):
+ """Classify failure type based on patterns"""
+ for failure_type, patterns in self.failure_patterns.items():
+ for pattern in patterns:
+ if re.search(pattern, line, re.IGNORECASE):
+ return failure_type
+ return None
+
+ def _report_failure(self, failure_type, line, source):
+ """Report a detected failure"""
+ report = {
+ "timestamp": datetime.now().isoformat(),
+ "failure_type": failure_type,
+ "source": source,
+ "line": line,
+ "recent_guest_logs": list(self.guest_log)[-20:],
+ "recent_host_events": list(self.host_events)[-20:],
+ }
+ print(f"\n{'='*60}")
+ print(f"FAILURE DETECTED: {failure_type}")
+ print(f"Source: {source}")
+ print(f"Line: {line}")
+ print(f"{'='*60}\n")
+
+ # Save failure report
+ report_dir = Path("/tmp/gsp_emu_failures")
+ report_dir.mkdir(exist_ok=True)
+ report_file = report_dir / f"failure_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
+ with open(report_file, 'w') as f:
+ json.dump(report, f, indent=2)
+ print(f"Failure report saved: {report_file}")
+
+ def _correlate_events(self, host_event):
+ """Correlate host tracepoint with recent guest activity"""
+ # Look for matching sequence IDs, RPC types, etc.
+ pass
+
+ def get_summary(self):
+ """Get monitoring summary"""
+ return {
+ "guest_log_entries": len(self.guest_log),
+ "host_events": len(self.host_events),
+ "recent_guest": list(self.guest_log)[-10:],
+ "recent_host": list(self.host_events)[-10:],
+ }
+
+def main():
+ import argparse
+ parser = argparse.ArgumentParser(description="Monitor guest NVIDIA driver initialization")
+ parser.add_argument("--guest-serial", default="/dev/ttyS0", help="Guest serial device")
+ parser.add_argument("--host-trace", default="/sys/kernel/debug/tracing/trace_pipe", help="Host trace pipe")
+ parser.add_argument("--duration", type=int, default=300, help="Monitor duration (seconds)")
+ args = parser.parse_args()
+
+ monitor = GuestInitMonitor(args.guest_serial, args.host_trace)
+ monitor.start()
+
+ try:
+ time.sleep(args.duration)
+ except KeyboardInterrupt:
+ pass
+ finally:
+ monitor.stop()
+ summary = monitor.get_summary()
+ print(json.dumps(summary, indent=2))
+
+if __name__ == "__main__":
+ main() \ No newline at end of file