From 6b4fdc8fee85e165488c54cd71af80520e46f031 Mon Sep 17 00:00:00 2001 From: Caleb Evans Date: Tue, 22 Jul 2025 15:16:59 +0000 Subject: [PATCH 1/4] feat(scoring): add frequency tracking service to calculate the frequency penalty --- .../service/FrequencyTrackingService.java | 154 ++++++++++++++++++ src/main/resources/application.properties | 5 + 2 files changed, 159 insertions(+) create mode 100644 src/main/java/com/redhat/podmortem/parser/service/FrequencyTrackingService.java diff --git a/src/main/java/com/redhat/podmortem/parser/service/FrequencyTrackingService.java b/src/main/java/com/redhat/podmortem/parser/service/FrequencyTrackingService.java new file mode 100644 index 0000000..4862e1d --- /dev/null +++ b/src/main/java/com/redhat/podmortem/parser/service/FrequencyTrackingService.java @@ -0,0 +1,154 @@ +package com.redhat.podmortem.parser.service; + +import com.redhat.podmortem.common.model.analysis.PatternFrequency; +import jakarta.enterprise.context.ApplicationScoped; +import java.time.Duration; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@ApplicationScoped +public class FrequencyTrackingService { + + private static final Logger log = LoggerFactory.getLogger(FrequencyTrackingService.class); + + private final Map patternFrequencies = new ConcurrentHashMap<>(); + + @ConfigProperty(name = "scoring.frequency.threshold", defaultValue = "10.0") + double frequencyThreshold; + + @ConfigProperty(name = "scoring.frequency.max-penalty", defaultValue = "0.8") + double maxPenalty; + + @ConfigProperty(name = "scoring.frequency.time-window-hours", defaultValue = "1") + int timeWindowHours; + + /** + * Records a pattern match for frequency tracking. + * + * @param patternId The ID of the pattern that was matched + */ + public void recordPatternMatch(String patternId) { + if (patternId == null || patternId.trim().isEmpty()) { + return; + } + + PatternFrequency frequency = + patternFrequencies.computeIfAbsent( + patternId, k -> new PatternFrequency(Duration.ofHours(timeWindowHours))); + + frequency.incrementCount(); + + log.debug( + "Recorded match for pattern '{}', current count: {}", + patternId, + frequency.getCurrentCount()); + } + + /** + * Calculates the frequency penalty for a given pattern. + * + * @param patternId The ID of the pattern + * @return Frequency penalty between 0.0 (no penalty) and maxPenalty (maximum penalty) + */ + public double calculateFrequencyPenalty(String patternId) { + if (patternId == null || patternId.trim().isEmpty()) { + return 0.0; + } + + PatternFrequency frequency = patternFrequencies.get(patternId); + if (frequency == null) { + return 0.0; // no matches recorded yet, no penalty + } + + double currentRate = frequency.getHourlyRate(); + + if (currentRate <= frequencyThreshold) { + return 0.0; // below threshold, no penalty + } + + // calculate penalty as a percentage of how much we exceed the threshold + // penalty = min(maxPenalty, (currentRate - threshold) / threshold * scaleFactor) + double excessRate = currentRate - frequencyThreshold; + double penalty = Math.min(maxPenalty, excessRate / frequencyThreshold); + + log.debug( + "Pattern '{}' frequency penalty: rate={}, threshold={}, penalty={}", + patternId, + currentRate, + frequencyThreshold, + penalty); + + return penalty; + } + + /** + * Gets the current frequency information for a pattern. + * + * @param patternId The ID of the pattern + * @return The PatternFrequency object, or null if not found + */ + public PatternFrequency getPatternFrequency(String patternId) { + return patternFrequencies.get(patternId); + } + + /** + * Gets current frequency statistics for all tracked patterns. + * + * @return Map of pattern IDs to their current match counts + */ + public Map getFrequencyStatistics() { + Map stats = new ConcurrentHashMap<>(); + patternFrequencies.forEach( + (patternId, frequency) -> stats.put(patternId, frequency.getCurrentCount())); + return stats; + } + + /** + * Resets frequency tracking for a specific pattern. + * + * @param patternId The ID of the pattern to reset + */ + public void resetPatternFrequency(String patternId) { + PatternFrequency frequency = patternFrequencies.get(patternId); + if (frequency != null) { + frequency.reset(); + log.info("Reset frequency tracking for pattern '{}'", patternId); + } + } + + /** Resets frequency tracking for all patterns. */ + public void resetAllFrequencies() { + patternFrequencies.clear(); + log.info("Reset frequency tracking for all patterns"); + } + + /** + * Gets the current frequency threshold. + * + * @return The frequency threshold + */ + public double getFrequencyThreshold() { + return frequencyThreshold; + } + + /** + * Gets the maximum penalty value. + * + * @return The maximum penalty + */ + public double getMaxPenalty() { + return maxPenalty; + } + + /** + * Gets the time window for frequency tracking. + * + * @return The time window in hours + */ + public int getTimeWindowHours() { + return timeWindowHours; + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 513ddad..38f5424 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -8,3 +8,8 @@ scoring.proximity.max-window=100 # Context keywords scoring.context.keywords-directory=keywords + +# Frequency tracking parameters for penalty calculation +scoring.frequency.threshold=10.0 +scoring.frequency.max-penalty=0.8 +scoring.frequency.time-window-hours=1 From 157046926272c735617bde66bd4fcf3568b2b517 Mon Sep 17 00:00:00 2001 From: Caleb Evans Date: Tue, 22 Jul 2025 15:17:13 +0000 Subject: [PATCH 2/4] chore(deps): bump common lib version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5ce5b54..beca00b 100644 --- a/pom.xml +++ b/pom.xml @@ -16,7 +16,7 @@ true 3.5.3 - 1.0-c839aae-SNAPSHOT + 1.0-13ea9c7-SNAPSHOT From 40f1f2b0e548ab0a66f5651171d43d780f12a994 Mon Sep 17 00:00:00 2001 From: Caleb Evans Date: Tue, 22 Jul 2025 15:17:37 +0000 Subject: [PATCH 3/4] feat(scoring): integrate all new scoring components into single calculation --- .../parser/service/ScoringService.java | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/redhat/podmortem/parser/service/ScoringService.java b/src/main/java/com/redhat/podmortem/parser/service/ScoringService.java index 407f37f..5f0aaaf 100644 --- a/src/main/java/com/redhat/podmortem/parser/service/ScoringService.java +++ b/src/main/java/com/redhat/podmortem/parser/service/ScoringService.java @@ -6,6 +6,7 @@ import com.redhat.podmortem.common.model.pattern.SequenceEvent; import com.redhat.podmortem.common.model.pattern.SequencePattern; import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; import java.util.List; import java.util.Map; import org.eclipse.microprofile.config.inject.ConfigProperty; @@ -32,6 +33,9 @@ public class ScoringService { @ConfigProperty(name = "scoring.proximity.max-window", defaultValue = "100") int maxWindow; + @Inject ContextAnalysisService contextAnalysisService; + @Inject FrequencyTrackingService frequencyTrackingService; + /** * Calculates the final score for a matched event. * @@ -53,16 +57,34 @@ public double calculateScore(MatchedEvent event, String[] allLines) { // calculate temporal factor using sequence pattern analysis double temporalFactor = calculateTemporalFactor(event, allLines); + // calculate context factor using keyword weighting + double contextFactor = contextAnalysisService.calculateContextFactor(event.getContext()); + + // calculate frequency penalty + double frequencyPenalty = + frequencyTrackingService.calculateFrequencyPenalty(pattern.getId()); + + // record this match for future frequency tracking + frequencyTrackingService.recordPatternMatch(pattern.getId()); + log.debug( - "Pattern '{}': Base Confidence={}, Severity Multiplier={}, Proximity Factor={}, Temporal Factor={}", + "Pattern '{}': Base Confidence={}, Severity Multiplier={}, Proximity Factor={}, Temporal Factor={}, Context Factor={}, Frequency Penalty={}", pattern.getName(), baseConfidence, severityMultiplier, proximityFactor, - temporalFactor); - - // final score calculation using multiplicative formula - double finalScore = baseConfidence * severityMultiplier * proximityFactor * temporalFactor; + temporalFactor, + contextFactor, + frequencyPenalty); + + // final score calculation + double finalScore = + baseConfidence + * severityMultiplier + * proximityFactor + * temporalFactor + * contextFactor + * (1.0 - frequencyPenalty); return finalScore; } From 982c35fe212995a61c9c7592cb86e08c93ca2697 Mon Sep 17 00:00:00 2001 From: Caleb Evans Date: Tue, 22 Jul 2025 15:19:08 +0000 Subject: [PATCH 4/4] chore(scoring): add some keywords with weights - Mostly for testing, I'm not sure these weights really are that accurate at the moment. --- src/main/resources/keywords/golang.json | 2 +- src/main/resources/keywords/java-spring.json | 2 +- src/main/resources/keywords/kubernetes.json | 2 +- src/main/resources/keywords/python.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/resources/keywords/golang.json b/src/main/resources/keywords/golang.json index 9a124f6..896c243 100644 --- a/src/main/resources/keywords/golang.json +++ b/src/main/resources/keywords/golang.json @@ -22,4 +22,4 @@ "heap": 0.1, "stack overflow": 0.4 } -} \ No newline at end of file +} diff --git a/src/main/resources/keywords/java-spring.json b/src/main/resources/keywords/java-spring.json index 7d98a1a..c9e8e51 100644 --- a/src/main/resources/keywords/java-spring.json +++ b/src/main/resources/keywords/java-spring.json @@ -62,4 +62,4 @@ "security": 0.2, "denied": 0.2 } -} \ No newline at end of file +} diff --git a/src/main/resources/keywords/kubernetes.json b/src/main/resources/keywords/kubernetes.json index 4cfa615..1472f8b 100644 --- a/src/main/resources/keywords/kubernetes.json +++ b/src/main/resources/keywords/kubernetes.json @@ -27,4 +27,4 @@ "ConfigMap": 0.05, "Secret": 0.1 } -} \ No newline at end of file +} diff --git a/src/main/resources/keywords/python.json b/src/main/resources/keywords/python.json index ceb6e5c..c4bbd1e 100644 --- a/src/main/resources/keywords/python.json +++ b/src/main/resources/keywords/python.json @@ -36,4 +36,4 @@ "timeout": 0.3, "blocked": 0.2 } -} \ No newline at end of file +}