ringkernel_procint/kernels/
pattern_detection.rs

1//! Pattern detection kernel.
2//!
3//! Detects process patterns from DFG nodes using GPU acceleration.
4
5use crate::cuda::{
6    generate_pattern_kernel, CpuFallbackExecutor, ExecutionResult, GpuStats, GpuStatus,
7    KernelExecutor,
8};
9use crate::models::{GpuDFGNode, GpuPatternMatch, PatternSeverity, PatternType};
10
11/// Pattern detection configuration.
12#[derive(Debug, Clone)]
13pub struct PatternConfig {
14    /// Bottleneck detection threshold (incoming rate).
15    pub bottleneck_threshold: f32,
16    /// Long-running duration threshold (ms).
17    pub duration_threshold: f32,
18    /// Minimum confidence to report pattern.
19    pub min_confidence: f32,
20    /// Maximum patterns to detect.
21    pub max_patterns: usize,
22}
23
24impl Default for PatternConfig {
25    fn default() -> Self {
26        Self {
27            bottleneck_threshold: 2.0,
28            duration_threshold: 60000.0, // 1 minute
29            min_confidence: 0.5,
30            max_patterns: 100,
31        }
32    }
33}
34
35/// Pattern detection kernel.
36pub struct PatternDetectionKernel {
37    /// Configuration.
38    config: PatternConfig,
39    /// Kernel executor.
40    executor: KernelExecutor,
41    /// Use GPU if available.
42    use_gpu: bool,
43    /// Whether kernel is compiled.
44    kernel_compiled: bool,
45}
46
47impl Default for PatternDetectionKernel {
48    fn default() -> Self {
49        Self::new(PatternConfig::default())
50    }
51}
52
53impl PatternDetectionKernel {
54    /// Create a new pattern detection kernel.
55    pub fn new(config: PatternConfig) -> Self {
56        let mut kernel = Self {
57            config,
58            executor: KernelExecutor::new(),
59            use_gpu: true,
60            kernel_compiled: false,
61        };
62
63        // Try to compile the CUDA kernel at creation time
64        kernel.try_compile_kernel();
65        kernel
66    }
67
68    /// Try to compile the CUDA kernel.
69    fn try_compile_kernel(&mut self) {
70        if self.executor.is_cuda_available() && !self.kernel_compiled {
71            let source = generate_pattern_kernel();
72            match self.executor.compile(&source) {
73                Ok(_) => {
74                    log::info!("Pattern detection CUDA kernel compiled successfully");
75                    self.kernel_compiled = true;
76                }
77                Err(e) => {
78                    log::warn!("Pattern detection CUDA kernel compilation failed: {}", e);
79                    self.kernel_compiled = false;
80                }
81            }
82        }
83    }
84
85    /// Disable GPU (use CPU fallback).
86    pub fn with_cpu_only(mut self) -> Self {
87        self.use_gpu = false;
88        self
89    }
90
91    /// Get GPU status.
92    pub fn gpu_status(&self) -> GpuStatus {
93        self.executor.gpu_status()
94    }
95
96    /// Get GPU stats.
97    pub fn gpu_stats(&self) -> &GpuStats {
98        &self.executor.stats
99    }
100
101    /// Check if GPU is being used.
102    pub fn is_using_gpu(&self) -> bool {
103        self.use_gpu && self.kernel_compiled && self.executor.is_cuda_available()
104    }
105
106    /// Detect patterns from DFG nodes.
107    pub fn detect(&mut self, nodes: &[GpuDFGNode]) -> PatternResult {
108        let start = std::time::Instant::now();
109
110        // Try GPU path first if available and compiled
111        #[cfg(feature = "cuda")]
112        let (gpu_patterns, exec_result) = if self.is_using_gpu() {
113            match self.executor.execute_pattern_gpu(
114                nodes,
115                self.config.bottleneck_threshold,
116                self.config.duration_threshold,
117            ) {
118                Ok((patterns, result)) => {
119                    log::debug!(
120                        "Pattern GPU execution: {} nodes -> {} patterns in {}µs",
121                        nodes.len(),
122                        patterns.len(),
123                        result.execution_time_us
124                    );
125                    (Some(patterns), result)
126                }
127                Err(e) => {
128                    log::warn!("Pattern GPU execution failed, falling back to CPU: {}", e);
129                    (None, ExecutionResult::default())
130                }
131            }
132        } else {
133            (None, ExecutionResult::default())
134        };
135
136        #[cfg(not(feature = "cuda"))]
137        let gpu_patterns: Option<Vec<GpuPatternMatch>> = None;
138        #[cfg(not(feature = "cuda"))]
139        let exec_result = ExecutionResult::default();
140
141        // Use GPU results or fall back to CPU
142        let (mut patterns, exec_result) = if let Some(gpu_patterns) = gpu_patterns {
143            (gpu_patterns, exec_result)
144        } else {
145            let mut patterns = Vec::with_capacity(self.config.max_patterns);
146            let result = CpuFallbackExecutor::execute_pattern_detection(
147                nodes,
148                &mut patterns,
149                self.config.bottleneck_threshold,
150                self.config.duration_threshold,
151            );
152            (patterns, result)
153        };
154
155        // Additional pattern detection: loops and rework
156        self.detect_loop_patterns(nodes, &mut patterns);
157
158        // Filter by confidence
159        patterns.retain(|p| p.confidence >= self.config.min_confidence);
160
161        // Limit results
162        patterns.truncate(self.config.max_patterns);
163
164        let total_time = start.elapsed().as_micros() as u64;
165
166        PatternResult {
167            patterns,
168            execution_result: exec_result,
169            total_time_us: total_time,
170        }
171    }
172
173    /// Detect loop patterns from node self-transitions.
174    fn detect_loop_patterns(&self, nodes: &[GpuDFGNode], patterns: &mut Vec<GpuPatternMatch>) {
175        for node in nodes {
176            // Check for high frequency self-activity (rework indicator)
177            if node.incoming_count > 0 && node.outgoing_count > 0 {
178                // Use degree ratio as a proxy for loop detection
179                let in_count = node.incoming_count as f32;
180                let out_count = node.outgoing_count as f32;
181                let degree_ratio = in_count.min(out_count) / in_count.max(out_count).max(1.0);
182                if degree_ratio > 0.3 && node.event_count > 10 {
183                    let mut pattern =
184                        GpuPatternMatch::new(PatternType::Loop, PatternSeverity::Warning);
185                    pattern.add_activity(node.activity_id);
186                    pattern.confidence = degree_ratio;
187                    pattern.frequency = node.event_count;
188                    pattern.avg_duration_ms = node.avg_duration_ms;
189                    patterns.push(pattern);
190                }
191            }
192        }
193    }
194}
195
196/// Result of pattern detection.
197#[derive(Debug)]
198pub struct PatternResult {
199    /// Detected patterns.
200    pub patterns: Vec<GpuPatternMatch>,
201    /// Kernel execution result.
202    pub execution_result: ExecutionResult,
203    /// Total processing time in microseconds.
204    pub total_time_us: u64,
205}
206
207impl PatternResult {
208    /// Get patterns by type.
209    pub fn by_type(&self, pattern_type: PatternType) -> Vec<&GpuPatternMatch> {
210        self.patterns
211            .iter()
212            .filter(|p| p.get_pattern_type() == pattern_type)
213            .collect()
214    }
215
216    /// Get patterns by severity.
217    pub fn by_severity(&self, severity: PatternSeverity) -> Vec<&GpuPatternMatch> {
218        self.patterns
219            .iter()
220            .filter(|p| p.get_severity() == severity)
221            .collect()
222    }
223
224    /// Count patterns by type.
225    pub fn count_by_type(&self) -> std::collections::HashMap<PatternType, usize> {
226        let mut counts = std::collections::HashMap::new();
227        for pattern in &self.patterns {
228            *counts.entry(pattern.get_pattern_type()).or_insert(0) += 1;
229        }
230        counts
231    }
232}
233
234#[cfg(test)]
235mod tests {
236    use super::*;
237
238    fn create_test_nodes() -> Vec<GpuDFGNode> {
239        vec![
240            GpuDFGNode {
241                activity_id: 1,
242                event_count: 100,
243                avg_duration_ms: 5000.0,
244                incoming_count: 5, // High incoming
245                outgoing_count: 1, // Low outgoing (bottleneck)
246                ..Default::default()
247            },
248            GpuDFGNode {
249                activity_id: 2,
250                event_count: 50,
251                avg_duration_ms: 120000.0, // Long running
252                incoming_count: 2,
253                outgoing_count: 2,
254                ..Default::default()
255            },
256        ]
257    }
258
259    #[test]
260    fn test_pattern_detection() {
261        let mut kernel = PatternDetectionKernel::default().with_cpu_only();
262        let nodes = create_test_nodes();
263        let result = kernel.detect(&nodes);
264
265        // Should detect bottleneck and long-running patterns
266        assert!(!result.patterns.is_empty()); // At least long-running
267    }
268
269    #[test]
270    fn test_pattern_filtering() {
271        let mut kernel = PatternDetectionKernel::default().with_cpu_only();
272        let nodes = create_test_nodes();
273        let result = kernel.detect(&nodes);
274
275        let long_running = result.by_type(PatternType::LongRunning);
276        assert!(!long_running.is_empty());
277    }
278}