@@ -13,16 +13,17 @@ namespace Microsoft.ML.TimeSeries
13
13
public class RootCauseAnalyzer
14
14
{
15
15
private static double _anomalyRatioThreshold = 0.5 ;
16
- private static double _anomalyDeltaThreshold = 0.95 ;
17
16
private static double _anomalyPreDeltaThreshold = 2 ;
18
17
19
18
private RootCauseLocalizationInput _src ;
20
19
private double _beta ;
20
+ private double _rootCauseThreshold ;
21
21
22
- public RootCauseAnalyzer ( RootCauseLocalizationInput src , double beta )
22
+ public RootCauseAnalyzer ( RootCauseLocalizationInput src , double beta , double rootCauseThreshold )
23
23
{
24
24
_src = src ;
25
25
_beta = beta ;
26
+ _rootCauseThreshold = rootCauseThreshold ;
26
27
}
27
28
28
29
public RootCause Analyze ( )
@@ -39,10 +40,10 @@ private RootCause AnalyzeOneLayer(RootCauseLocalizationInput src)
39
40
dst . Items = new List < RootCauseItem > ( ) ;
40
41
41
42
DimensionInfo dimensionInfo = SeparateDimension ( src . AnomalyDimension , src . AggregateSymbol ) ;
42
- Tuple < PointTree , PointTree , Dictionary < string , TimeSeriesPoint > > pointInfo = GetPointsInfo ( src , dimensionInfo ) ;
43
+ Tuple < PointTree , PointTree , Dictionary < Dictionary < string , object > , TimeSeriesPoint > > pointInfo = GetPointsInfo ( src , dimensionInfo ) ;
43
44
PointTree pointTree = pointInfo . Item1 ;
44
45
PointTree anomalyTree = pointInfo . Item2 ;
45
- Dictionary < string , TimeSeriesPoint > dimPointMapping = pointInfo . Item3 ;
46
+ Dictionary < Dictionary < string , Object > , TimeSeriesPoint > dimPointMapping = pointInfo . Item3 ;
46
47
47
48
//which means there is no anomaly point with the anomaly dimension or no point under anomaly dimension
48
49
if ( anomalyTree . ParentNode == null || dimPointMapping . Count == 0 )
@@ -81,11 +82,12 @@ private DimensionInfo SeparateDimension(Dictionary<string, Object> dimensions, O
81
82
return info ;
82
83
}
83
84
84
- private Tuple < PointTree , PointTree , Dictionary < string , TimeSeriesPoint > > GetPointsInfo ( RootCauseLocalizationInput src , DimensionInfo dimensionInfo )
85
+ private Tuple < PointTree , PointTree , Dictionary < Dictionary < string , object > , TimeSeriesPoint > > GetPointsInfo ( RootCauseLocalizationInput src , DimensionInfo dimensionInfo )
85
86
{
86
87
PointTree pointTree = new PointTree ( ) ;
87
88
PointTree anomalyTree = new PointTree ( ) ;
88
- Dictionary < string , TimeSeriesPoint > dimPointMapping = new Dictionary < string , TimeSeriesPoint > ( ) ;
89
+ DimensionComparer dc = new DimensionComparer ( ) ;
90
+ Dictionary < Dictionary < string , object > , TimeSeriesPoint > dimPointMapping = new Dictionary < Dictionary < string , object > , TimeSeriesPoint > ( dc ) ;
89
91
90
92
List < TimeSeriesPoint > totalPoints = GetTotalPointsForAnomalyTimestamp ( src ) ;
91
93
Dictionary < string , Object > subDim = GetSubDim ( src . AnomalyDimension , dimensionInfo . DetailDims ) ;
@@ -94,9 +96,9 @@ private Tuple<PointTree, PointTree, Dictionary<string, TimeSeriesPoint>> GetPoin
94
96
{
95
97
if ( ContainsAll ( point . Dimension , subDim ) )
96
98
{
97
- if ( ! dimPointMapping . ContainsKey ( GetDicCode ( point . Dimension ) ) )
99
+ if ( ! dimPointMapping . ContainsKey ( point . Dimension ) )
98
100
{
99
- dimPointMapping . Add ( GetDicCode ( point . Dimension ) , point ) ;
101
+ dimPointMapping . Add ( point . Dimension , point ) ;
100
102
bool isValidPoint = point . IsAnomaly == true ;
101
103
if ( ContainsAll ( point . Dimension , subDim ) )
102
104
{
@@ -111,7 +113,7 @@ private Tuple<PointTree, PointTree, Dictionary<string, TimeSeriesPoint>> GetPoin
111
113
}
112
114
}
113
115
114
- return new Tuple < PointTree , PointTree , Dictionary < string , TimeSeriesPoint > > ( pointTree , anomalyTree , dimPointMapping ) ;
116
+ return new Tuple < PointTree , PointTree , Dictionary < Dictionary < string , Object > , TimeSeriesPoint > > ( pointTree , anomalyTree , dimPointMapping ) ;
115
117
}
116
118
117
119
protected Dictionary < string , Object > GetSubDim ( Dictionary < string , Object > dimension , List < string > keyList )
@@ -327,7 +329,7 @@ private AnomalyDirection GetRootCauseDirection(TimeSeriesPoint rootCausePoint)
327
329
}
328
330
}
329
331
330
- private void GetRootCauseDirectionAndScore ( Dictionary < string , TimeSeriesPoint > dimPointMapping , Dictionary < string , Object > anomalyRoot , RootCause dst , double beta , PointTree pointTree , AggregateType aggType , Object aggSymbol )
332
+ private void GetRootCauseDirectionAndScore ( Dictionary < Dictionary < string , Object > , TimeSeriesPoint > dimPointMapping , Dictionary < string , Object > anomalyRoot , RootCause dst , double beta , PointTree pointTree , AggregateType aggType , Object aggSymbol )
331
333
{
332
334
TimeSeriesPoint anomalyPoint = GetPointByDimension ( dimPointMapping , anomalyRoot , pointTree , aggType , aggSymbol ) ;
333
335
if ( dst . Items . Count > 1 )
@@ -378,11 +380,11 @@ private void GetRootCauseDirectionAndScore(Dictionary<string, TimeSeriesPoint> d
378
380
}
379
381
}
380
382
381
- private TimeSeriesPoint GetPointByDimension ( Dictionary < string , TimeSeriesPoint > dimPointMapping , Dictionary < string , Object > dimension , PointTree pointTree , AggregateType aggType , Object aggSymbol )
383
+ private TimeSeriesPoint GetPointByDimension ( Dictionary < Dictionary < string , Object > , TimeSeriesPoint > dimPointMapping , Dictionary < string , Object > dimension , PointTree pointTree , AggregateType aggType , Object aggSymbol )
382
384
{
383
- if ( dimPointMapping . ContainsKey ( GetDicCode ( dimension ) ) )
385
+ if ( dimPointMapping . ContainsKey ( dimension ) )
384
386
{
385
- return dimPointMapping [ GetDicCode ( dimension ) ] ;
387
+ return dimPointMapping [ dimension ] ;
386
388
}
387
389
388
390
int count = 0 ;
@@ -419,11 +421,6 @@ private TimeSeriesPoint GetPointByDimension(Dictionary<string, TimeSeriesPoint>
419
421
}
420
422
}
421
423
422
- private static string GetDicCode ( Dictionary < string , Object > dic )
423
- {
424
- return string . Join ( ";" , dic . Select ( x => x . Key + "=" + ( string ) x . Value ) . ToArray ( ) ) ;
425
- }
426
-
427
424
private void BuildTree ( PointTree tree , List < string > aggDims , TimeSeriesPoint point , Object aggSymbol )
428
425
{
429
426
int aggNum = 0 ;
@@ -476,22 +473,23 @@ private BestDimension FindBestDimension(SortedDictionary<BestDimension, double>
476
473
bool isRatioNan = Double . IsNaN ( valueRatioMap [ best ] ) ;
477
474
if ( dimension . Key . AnomalyDis . Count > 1 )
478
475
{
479
- if ( ! isRatioNan && ( best . AnomalyDis . Count != 1 && ( isLeavesLevel ? valueRatioMap [ best ] . CompareTo ( dimension . Value ) <= 0 : valueRatioMap [ best ] . CompareTo ( dimension . Value ) >= 0 ) ) )
476
+ if ( best . AnomalyDis . Count != 1 && ! isRatioNan && ( isLeavesLevel ? valueRatioMap [ best ] . CompareTo ( dimension . Value ) <= 0 : valueRatioMap [ best ] . CompareTo ( dimension . Value ) >= 0 ) )
480
477
{
481
- best = dimension . Key ;
478
+ best = GetBestDimension ( best , dimension , valueRatioMap ) ;
482
479
}
483
480
}
484
- else
481
+ else if ( dimension . Key . AnomalyDis . Count == 1 )
485
482
{
483
+
486
484
if ( best . AnomalyDis . Count > 1 )
487
485
{
488
486
best = dimension . Key ;
489
487
}
490
- else
488
+ else if ( best . AnomalyDis . Count == 1 )
491
489
{
492
490
if ( ! isRatioNan && ( isLeavesLevel ? valueRatioMap [ best ] . CompareTo ( dimension . Value ) <= 0 : valueRatioMap [ best ] . CompareTo ( dimension . Value ) >= 0 ) )
493
491
{
494
- best = dimension . Key ;
492
+ best = GetBestDimension ( best , dimension , valueRatioMap ) ;
495
493
}
496
494
}
497
495
}
@@ -502,6 +500,22 @@ private BestDimension FindBestDimension(SortedDictionary<BestDimension, double>
502
500
return best ;
503
501
}
504
502
503
+ private BestDimension GetBestDimension ( BestDimension best , KeyValuePair < BestDimension , double > dimension , Dictionary < BestDimension , Double > valueRatioMap )
504
+ {
505
+ if ( valueRatioMap [ best ] . CompareTo ( dimension . Value ) == 0 )
506
+ {
507
+ if ( dimension . Key . AnomalyDis . Count != dimension . Key . PointDis . Count )
508
+ {
509
+ best = dimension . Key ;
510
+ }
511
+ }
512
+ else
513
+ {
514
+ best = dimension . Key ;
515
+ }
516
+ return best ;
517
+ }
518
+
505
519
/// <summary>
506
520
/// Calculate the surprise score according to root cause point and anomaly point
507
521
/// </summary>
@@ -569,6 +583,10 @@ private double GetFinalScore(double surprise, double ep, double beta)
569
583
else
570
584
{
571
585
a = ( 1 - Math . Pow ( 2 , - surprise ) ) ;
586
+ if ( Double . IsNaN ( a ) )
587
+ {
588
+ a = 1 ;
589
+ }
572
590
b = ( 1 - Math . Pow ( 2 , - ep ) ) ;
573
591
}
574
592
@@ -593,7 +611,7 @@ private static Dictionary<string, Object> UpdateDimensionValue(Dictionary<string
593
611
594
612
private bool StopAnomalyComparison ( double preTotal , double parent , double current , double pre )
595
613
{
596
- if ( Math . Abs ( preTotal ) < Math . Abs ( parent ) * _anomalyDeltaThreshold )
614
+ if ( Math . Abs ( preTotal ) < Math . Abs ( parent ) * _rootCauseThreshold )
597
615
{
598
616
return false ;
599
617
}
@@ -603,7 +621,7 @@ private bool StopAnomalyComparison(double preTotal, double parent, double curren
603
621
604
622
private bool ShouldSeparateAnomaly ( double total , double parent , int totalSize , int size )
605
623
{
606
- if ( Math . Abs ( total ) < Math . Abs ( parent ) * _anomalyDeltaThreshold )
624
+ if ( Math . Abs ( total ) < Math . Abs ( parent ) * _rootCauseThreshold )
607
625
{
608
626
return false ;
609
627
}
@@ -657,7 +675,7 @@ private void UpdateDistribution(Dictionary<string, int> distribution, List<TimeS
657
675
{
658
676
foreach ( TimeSeriesPoint point in points )
659
677
{
660
- string dimVal = ( string ) point . Dimension [ dimKey ] ;
678
+ string dimVal = Convert . ToString ( point . Dimension [ dimKey ] ) ;
661
679
if ( ! distribution . ContainsKey ( dimVal ) )
662
680
{
663
681
distribution . Add ( dimVal , 0 ) ;
@@ -684,7 +702,7 @@ private static bool ContainsAll(Dictionary<string, Object> bigDictionary, Dictio
684
702
685
703
private bool IsAggregationDimension ( Object val , Object aggSymbol )
686
704
{
687
- return val . Equals ( aggSymbol ) ;
705
+ return Convert . ToString ( val ) . Equals ( aggSymbol ) ;
688
706
}
689
707
}
690
708
@@ -748,4 +766,47 @@ public RootCauseScore(double surprise, double explanatoryScore)
748
766
ExplanatoryScore = explanatoryScore ;
749
767
}
750
768
}
751
- }
769
+
770
+ internal class DimensionComparer : EqualityComparer < Dictionary < string , object > >
771
+ {
772
+ public override bool Equals ( Dictionary < string , object > x , Dictionary < string , object > y )
773
+ {
774
+ if ( x == null && y == null )
775
+ {
776
+ return true ;
777
+ }
778
+ if ( ( x == null && y != null ) || ( x != null && y == null ) )
779
+ {
780
+ return false ;
781
+ }
782
+ if ( x . Count != y . Count )
783
+ {
784
+ return false ;
785
+ }
786
+ if ( x . Keys . Except ( y . Keys ) . Any ( ) )
787
+ {
788
+ return false ;
789
+ }
790
+ if ( y . Keys . Except ( x . Keys ) . Any ( ) )
791
+ {
792
+ return false ;
793
+ }
794
+ foreach ( var pair in x )
795
+ {
796
+ if ( ! pair . Value . Equals ( y [ pair . Key ] ) )
797
+ {
798
+ return false ;
799
+ }
800
+ }
801
+ return true ;
802
+ }
803
+
804
+ public override int GetHashCode ( Dictionary < string , object > obj )
805
+ {
806
+ int code = 0 ;
807
+ foreach ( KeyValuePair < string , object > pair in obj )
808
+ code = code ^ pair . GetHashCode ( ) ;
809
+ return code ;
810
+ }
811
+ }
812
+ }
0 commit comments