@@ -38,10 +38,73 @@ public static void Score(ParsedPlan plan)
3838
3939 if ( stmt . WaitStats . Count > 0 && stmt . QueryTimeStats != null )
4040 ScoreWaitStats ( stmt ) ;
41+
42+ if ( stmt . WaitStats . Count > 0 )
43+ EmitWaitStatWarnings ( stmt ) ;
44+ }
45+ }
46+ }
47+
48+ /// <summary>
49+ /// Emits a PlanWarning per wait stat entry, merging the per-wait benefit %
50+ /// from ScoreWaitStats with the descriptive content from WaitStatsKnowledge.
51+ /// The existing wait-stats chart/card stays as a complementary view.
52+ /// </summary>
53+ private static void EmitWaitStatWarnings ( PlanStatement stmt )
54+ {
55+ // Lookup benefit % by wait type (populated by ScoreWaitStats)
56+ var benefitByType = new Dictionary < string , double > ( StringComparer . OrdinalIgnoreCase ) ;
57+ foreach ( var wb in stmt . WaitBenefits )
58+ benefitByType [ wb . WaitType ] = wb . MaxBenefitPercent ;
59+
60+ foreach ( var wait in stmt . WaitStats )
61+ {
62+ if ( wait . WaitTimeMs <= 0 ) continue ;
63+
64+ var entry = WaitStatsKnowledge . Lookup ( wait . WaitType ) ;
65+ double ? benefitPct = benefitByType . TryGetValue ( wait . WaitType , out var b ) ? b : null ;
66+
67+ var msg = new System . Text . StringBuilder ( ) ;
68+ msg . Append ( wait . WaitType ) . Append ( ": " ) . Append ( entry . Description ) ;
69+ msg . Append ( " Observed " ) . Append ( wait . WaitTimeMs . ToString ( "N0" ) ) . Append ( " ms" ) ;
70+ if ( wait . WaitCount > 0 )
71+ msg . Append ( " across " ) . Append ( wait . WaitCount . ToString ( "N0" ) ) . Append ( " wait" ) . Append ( wait . WaitCount == 1 ? "" : "s" ) ;
72+ msg . Append ( '.' ) ;
73+
74+ if ( entry . ShowEffectiveLatency && wait . WaitCount > 0 )
75+ {
76+ var effLatency = ( double ) wait . WaitTimeMs / wait . WaitCount ;
77+ msg . Append ( " Effective latency: " )
78+ . Append ( FormatLatency ( effLatency ) )
79+ . Append ( " per wait." ) ;
4180 }
81+
82+ var severity = benefitPct switch
83+ {
84+ >= 50 => PlanWarningSeverity . Critical ,
85+ >= 10 => PlanWarningSeverity . Warning ,
86+ _ => PlanWarningSeverity . Info ,
87+ } ;
88+
89+ stmt . PlanWarnings . Add ( new PlanWarning
90+ {
91+ WarningType = "Wait: " + wait . WaitType ,
92+ Message = msg . ToString ( ) ,
93+ Severity = severity ,
94+ MaxBenefitPercent = benefitPct ,
95+ ActionableFix = entry . HowToFix
96+ } ) ;
4297 }
4398 }
4499
100+ private static string FormatLatency ( double ms )
101+ {
102+ if ( ms >= 1000 ) return $ "{ ms / 1000 : N2} s";
103+ if ( ms >= 10 ) return $ "{ ms : N0} ms";
104+ if ( ms >= 1 ) return $ "{ ms : N1} ms";
105+ return $ "{ ms * 1000 : N0} µs";
106+ }
107+
45108 private static void ScoreStatementWarnings ( PlanStatement stmt )
46109 {
47110 var elapsedMs = stmt . QueryTimeStats ? . ElapsedTimeMs ?? 0 ;
0 commit comments