1+ using System ;
2+ using System . Collections . Concurrent ;
3+ using System . Collections . Generic ;
4+ using System . Text ;
5+ using System . Threading . Tasks ;
6+ using System . Timers ;
7+ using System . Linq ;
8+ using Microsoft . Extensions . Logging ;
9+
10+ namespace Vtex . SplunkLogger
11+ {
12+ /// <summary>
13+ /// This class contains all methods and logics necessary to control kpi process.
14+ /// </summary>
15+ /// <remarks>
16+ /// MetricManager is used to summarize performance indicators during one minute
17+ /// and them dispatch those entries to Splunk as a "custom" log.
18+ /// </remarks>
19+ class MetricManager
20+ {
21+ #region [ Private Constants ]
22+
23+ internal const string METRIC_SPLIT = "-#-" ;
24+ internal const string OPTIONAL_SPLIT = ":#:" ;
25+ internal const string DIMENSION_SPLIT = "|#|" ;
26+
27+ #endregion
28+
29+ #region [ Private Kpi Ready Event ]
30+
31+ event EventHandler < VTEXKpiEntry > kpiReady = delegate { } ;
32+
33+ #endregion
34+
35+ #region [ Private Static Fields ]
36+
37+ static readonly ConcurrentDictionary < string , Tuple < ulong , float > > items = new ConcurrentDictionary < string , Tuple < ulong , float > > ( ) ;
38+ static readonly ConcurrentDictionary < string , float > itemsMax = new ConcurrentDictionary < string , float > ( ) ;
39+ static readonly ConcurrentDictionary < string , float > itemsMin = new ConcurrentDictionary < string , float > ( ) ;
40+
41+ #endregion
42+
43+ #region [ Private Fields ]
44+
45+ readonly OnMinuteClockTimer summarizerTimer ;
46+ readonly ILogger logger ;
47+
48+ #endregion
49+
50+ #region [ Internal Constructor ]
51+
52+ internal MetricManager ( ILogger logger , EventHandler < VTEXKpiEntry > kpiReady )
53+ {
54+ this . logger = logger ;
55+
56+ summarizerTimer = new OnMinuteClockTimer ( ) ;
57+ summarizerTimer . Elapsed += SummarizerTimer_Elapsed ;
58+ summarizerTimer . Start ( ) ;
59+
60+ this . kpiReady = kpiReady ;
61+ }
62+
63+ #endregion
64+
65+ #region [ Internal Methods ]
66+
67+ internal void RegisterKpi ( string kpiName , float kpiValue , string account = "" , params Tuple < string , string > [ ] extraParameters )
68+ {
69+
70+ var extraFields = new Dictionary < string , string > ( ) ;
71+
72+ if ( extraParameters != null && extraParameters . Length > 0 )
73+ extraParameters . ToList ( ) . ForEach ( tuple => extraFields . Add ( tuple . Item1 , tuple . Item2 ) ) ;
74+
75+ if ( ! string . IsNullOrWhiteSpace ( account ) )
76+ extraFields . Add ( "account" , account ) ;
77+
78+ RegisterKpi ( kpiName , kpiValue , extraFields ) ;
79+ }
80+
81+ #endregion
82+
83+ #region [ Private Methods ]
84+
85+ void RegisterKpi ( string metricName , float metricValue , Dictionary < string , string > extraFields )
86+ {
87+ var clonedExtraFields = new Dictionary < string , string > ( ) ;
88+ extraFields . All ( a => { clonedExtraFields . Add ( a . Key , a . Value ) ; return true ; } ) ;
89+
90+ string itemKey = GetKey ( metricName , clonedExtraFields ) ;
91+ Tuple < ulong , float > metricItem = new Tuple < ulong , float > ( 1 , metricValue ) ;
92+
93+ items . AddOrUpdate ( itemKey , metricItem , ( key , metric ) =>
94+ {
95+ return new Tuple < ulong , float > ( metric . Item1 + metricItem . Item1 , metric . Item2 + metricItem . Item2 ) ;
96+ } ) ;
97+
98+ itemsMax . AddOrUpdate ( itemKey , metricValue , ( key , metric ) =>
99+ {
100+ if ( itemsMax . ContainsKey ( itemKey ) )
101+ {
102+ float oldValue = itemsMax [ itemKey ] ;
103+ return metricValue > oldValue ? metricValue : oldValue ;
104+ }
105+
106+ return metric ;
107+ } ) ;
108+
109+ itemsMin . AddOrUpdate ( itemKey , metricValue , ( key , metric ) =>
110+ {
111+ if ( itemsMin . ContainsKey ( itemKey ) )
112+ {
113+ float oldValue = itemsMin [ itemKey ] ;
114+ return metricValue < oldValue ? metricValue : oldValue ;
115+ }
116+
117+ return metric ;
118+ } ) ;
119+ }
120+
121+ void SummarizerTimer_Elapsed ( object sender , ElapsedEventArgs e )
122+ {
123+ if ( items . Keys . Count > 0 )
124+ {
125+ List < VTEXKpiEntry > kpiMetrics = new List < VTEXKpiEntry > ( ) ;
126+ Parallel . ForEach ( items . Keys , key =>
127+ {
128+ if ( ! string . IsNullOrWhiteSpace ( key ) )
129+ {
130+ Tuple < ulong , float > valueTuple = null ;
131+ items . TryRemove ( key , out valueTuple ) ;
132+
133+ float maxValue = 0 ;
134+ itemsMax . TryRemove ( key , out maxValue ) ;
135+
136+ float minValue = 0 ;
137+ itemsMin . TryRemove ( key , out minValue ) ;
138+
139+ var kpiEntry = GenerateEntry ( key , valueTuple , maxValue , minValue ) ;
140+ kpiReady ( logger , kpiEntry ) ;
141+ }
142+ } ) ;
143+ }
144+ }
145+
146+ VTEXKpiEntry GenerateEntry ( string key , Tuple < ulong , float > valueTuple , float maxValue , float minValue )
147+ {
148+ string metricName = string . Empty ;
149+ Dictionary < string , string > extraFields = null ;
150+ RetreiveKeyItems ( key , out metricName , out extraFields ) ;
151+
152+ VTEXKpiEntry entry = new VTEXKpiEntry ( metricName )
153+ {
154+ Count = valueTuple . Item1 ,
155+ Name = metricName ,
156+ Sum = valueTuple . Item2 ,
157+ Max = maxValue ,
158+ Min = minValue
159+ } ;
160+
161+ if ( extraFields != null && extraFields . Count > 0 )
162+ {
163+ if ( extraFields . ContainsKey ( "account" ) )
164+ {
165+ entry . Account = extraFields [ "account" ] ;
166+ extraFields . Remove ( "account" ) ;
167+ }
168+ }
169+
170+ entry . ExtraParameters = extraFields ;
171+
172+ return entry ;
173+ }
174+
175+ void RetreiveKeyItems ( string key , out string metricName , out Dictionary < string , string > customFields )
176+ {
177+ metricName = string . Empty ;
178+ customFields = null ;
179+ string [ ] parts = key . Split ( new string [ ] { METRIC_SPLIT } , StringSplitOptions . RemoveEmptyEntries ) ;
180+ metricName = parts [ 0 ] ;
181+ if ( parts . Length > 1 )
182+ {
183+ customFields = new Dictionary < string , string > ( ) ;
184+ string [ ] metricParts = parts [ 1 ] . Split ( new string [ ] { OPTIONAL_SPLIT } , StringSplitOptions . RemoveEmptyEntries ) ;
185+ string [ ] dimensionParts = null ;
186+ foreach ( string metricPart in metricParts )
187+ {
188+ dimensionParts = metricPart . Split ( new string [ ] { DIMENSION_SPLIT } , StringSplitOptions . RemoveEmptyEntries ) ;
189+ if ( dimensionParts . Length == 2 )
190+ customFields . Add ( dimensionParts [ 0 ] , dimensionParts [ 1 ] ) ;
191+ else
192+ customFields . Add ( dimensionParts [ 0 ] , string . Empty ) ;
193+ }
194+ }
195+ }
196+
197+ string GetKey ( string metricName , Dictionary < string , string > extraFields = null )
198+ {
199+ if ( extraFields != null && extraFields . Count > 0 )
200+ {
201+ StringBuilder stringBuilder = new StringBuilder ( ) ;
202+
203+ foreach ( KeyValuePair < string , string > item in extraFields )
204+ {
205+ stringBuilder . AppendFormat ( "{0}{1}{2}" , item . Key , DIMENSION_SPLIT , item . Value ) ;
206+ stringBuilder . Append ( OPTIONAL_SPLIT ) ;
207+ }
208+ string itemString = stringBuilder . ToString ( ) ;
209+ itemString = string . Format ( "{0}{1}{2}" , metricName , METRIC_SPLIT , itemString . Remove ( itemString . Length - OPTIONAL_SPLIT . Length , OPTIONAL_SPLIT . Length ) ) ;
210+ return itemString ;
211+ }
212+
213+ return metricName ;
214+ }
215+
216+ #endregion
217+ }
218+ }
0 commit comments