22// Licensed under the MIT License. See License.txt in the project root for license information.
33
44using System ;
5+ using System . Collections . Concurrent ;
56using System . Collections . Generic ;
67using System . Linq ;
78using System . Threading ;
89using System . Threading . Tasks ;
910using Microsoft . Azure . WebJobs . Script ;
1011using Microsoft . Azure . WebJobs . Script . Description ;
12+ using Microsoft . Azure . WebJobs . Script . Diagnostics ;
1113using Microsoft . Diagnostics . Tracing ;
14+ using WebJobs . Script . WebHost . Models ;
1215
1316namespace WebJobs . Script . WebHost . Diagnostics
1417{
1518 public static class MetricsEventManager
1619 {
1720 private static FunctionActivityTracker instance = null ;
1821 private static object functionActivityTrackerLockObject = new object ( ) ;
22+ private static string siteName ;
1923
20- public static void FunctionStarted ( )
24+ static MetricsEventManager ( )
25+ {
26+ siteName = GetNormalizedString ( Environment . GetEnvironmentVariable ( "WEBSITE_SITE_NAME" ) ) ;
27+ }
28+
29+ public static void FunctionStarted ( FunctionStartedEvent startedEvent )
2130 {
2231 lock ( functionActivityTrackerLockObject )
2332 {
2433 if ( instance == null )
2534 {
2635 instance = new FunctionActivityTracker ( ) ;
2736 }
28- instance . FunctionStarted ( ) ;
37+ instance . FunctionStarted ( startedEvent ) ;
2938 }
3039 }
3140
32- public static void FunctionCompleted ( )
41+ public static void FunctionCompleted ( FunctionStartedEvent completedEvent )
3342 {
3443 lock ( functionActivityTrackerLockObject )
3544 {
3645 if ( instance != null )
3746 {
38- instance . FunctionCompleted ( ) ;
47+ instance . FunctionCompleted ( completedEvent ) ;
3948 if ( ! instance . IsActive )
4049 {
4150 instance . StopEtwTaskAndRaiseFinishedEvent ( ) ;
@@ -52,7 +61,6 @@ public static void HostStarted(ScriptHost scriptHost)
5261 return ;
5362 }
5463
55- var siteName = GetNormalizedString ( Environment . GetEnvironmentVariable ( "WEBSITE_SITE_NAME" ) ) ;
5664 foreach ( var function in scriptHost . Functions )
5765 {
5866 if ( function == null || function . Metadata == null )
@@ -97,7 +105,9 @@ private class FunctionActivityTracker : IDisposable
97105 private DateTime startTime = DateTime . UtcNow ;
98106 private ulong totalExecutionCount = 0 ;
99107 private ulong runningFunctionCount = 0 ;
108+ private const int MetricEventIntervalInSeconds = 5 ;
100109 private CancellationTokenSource etwTaskcancellationSource = new CancellationTokenSource ( ) ;
110+ private ConcurrentQueue < FunctionMetrics > functionMetricsQueue = new ConcurrentQueue < FunctionMetrics > ( ) ;
101111
102112 internal FunctionActivityTracker ( )
103113 {
@@ -106,10 +116,18 @@ internal FunctionActivityTracker()
106116 {
107117 try
108118 {
119+ int currentSecond = MetricEventIntervalInSeconds ;
109120 while ( ! etwTaskcancellationSource . Token . IsCancellationRequested )
110121 {
111- RaiseMetricEtwEvent ( ExecutionStage . InProgress ) ;
112- await Task . Delay ( TimeSpan . FromSeconds ( 5 ) , etwTaskcancellationSource . Token ) ;
122+ RaiseMetricsPerFunctionEvent ( ) ;
123+ currentSecond = currentSecond + 1 ;
124+ if ( currentSecond >= MetricEventIntervalInSeconds )
125+ {
126+ RaiseMetricEtwEvent ( ExecutionStage . InProgress ) ;
127+ currentSecond = 0 ;
128+ }
129+
130+ await Task . Delay ( TimeSpan . FromSeconds ( 1 ) , etwTaskcancellationSource . Token ) ;
113131 }
114132 }
115133 catch ( TaskCanceledException )
@@ -120,13 +138,7 @@ internal FunctionActivityTracker()
120138 } ,
121139 etwTaskcancellationSource . Token ) ;
122140 }
123-
124- private enum ExecutionStage
125- {
126- InProgress ,
127- Finished
128- }
129-
141+
130142 internal bool IsActive
131143 {
132144 get
@@ -149,23 +161,33 @@ public void Dispose()
149161 GC . SuppressFinalize ( this ) ;
150162 }
151163
152- internal void FunctionStarted ( )
153- {
164+ internal void FunctionStarted ( FunctionStartedEvent startedEvent )
165+ {
154166 totalExecutionCount ++ ;
155167 runningFunctionCount ++ ;
156- }
157168
158- internal void FunctionCompleted ( )
169+ var metricEventPerFunction = new FunctionMetrics ( startedEvent . FunctionMetadata . Name , ExecutionStage . Started , 0 ) ;
170+ functionMetricsQueue . Enqueue ( metricEventPerFunction ) ;
171+ }
172+
173+ internal void FunctionCompleted ( FunctionStartedEvent completedEvent )
159174 {
160175 if ( runningFunctionCount > 0 )
161176 {
162177 runningFunctionCount -- ;
163178 }
179+
180+ var functionStage = ( completedEvent . Success == false ) ? ExecutionStage . Failed : ExecutionStage . Succeeded ;
181+ long executionTimeInMS = ( long ) completedEvent . EndTime . Subtract ( completedEvent . StartTime ) . TotalMilliseconds ;
182+
183+ var monitoringEvent = new FunctionMetrics ( completedEvent . FunctionMetadata . Name , functionStage , executionTimeInMS ) ;
184+ functionMetricsQueue . Enqueue ( monitoringEvent ) ;
164185 }
165186
166187 internal void StopEtwTaskAndRaiseFinishedEvent ( )
167188 {
168189 etwTaskcancellationSource . Cancel ( ) ;
190+ RaiseMetricsPerFunctionEvent ( ) ;
169191 RaiseMetricEtwEvent ( ExecutionStage . Finished ) ;
170192 }
171193
@@ -180,6 +202,44 @@ private static void WriteFunctionsMetricEvent(string executionId, ulong executio
180202 {
181203 MetricEventSource . Log . RaiseFunctionsMetricEvent ( executionId , executionTimeSpan , executionCount , executionStage ) ;
182204 }
205+
206+ private void RaiseMetricsPerFunctionEvent ( )
207+ {
208+ List < FunctionMetrics > metricsEventsList = GetMetricsQueueSnapshot ( ) ;
209+
210+ var aggregatedEventsPerFunction = from item in metricsEventsList
211+ group item by item . FunctionName into FunctionGroups
212+ select new
213+ {
214+ FunctionName = FunctionGroups . Key ,
215+ StartedCount = Convert . ToUInt64 ( FunctionGroups . Count ( x => x . ExecutionStage == ExecutionStage . Started ) ) ,
216+ FailedCount = Convert . ToUInt64 ( FunctionGroups . Count ( x => x . ExecutionStage == ExecutionStage . Failed ) ) ,
217+ SucceededCount = Convert . ToUInt64 ( FunctionGroups . Count ( x => x . ExecutionStage == ExecutionStage . Succeeded ) ) ,
218+ TotalExectionTimeInMs = Convert . ToUInt64 ( FunctionGroups . Sum ( x => Convert . ToDecimal ( x . ExecutionTimeInMS ) ) )
219+ } ;
220+
221+ foreach ( var functionEvent in aggregatedEventsPerFunction )
222+ {
223+ MetricEventSource . Log . RaiseMetricsPerFunctionEvent ( siteName , functionEvent . FunctionName , functionEvent . TotalExectionTimeInMs , functionEvent . StartedCount , functionEvent . SucceededCount , functionEvent . FailedCount ) ;
224+ }
225+ }
226+
227+ private List < FunctionMetrics > GetMetricsQueueSnapshot ( )
228+ {
229+ var queueSnapshot = new List < FunctionMetrics > ( ) ;
230+ var currentQueueLength = functionMetricsQueue . Count ;
231+
232+ for ( int iterator = 0 ; iterator < currentQueueLength ; iterator ++ )
233+ {
234+ FunctionMetrics queueItem ;
235+ if ( functionMetricsQueue . TryDequeue ( out queueItem ) )
236+ {
237+ queueSnapshot . Add ( queueItem ) ;
238+ }
239+ }
240+
241+ return queueSnapshot ;
242+ }
183243 }
184244
185245 [ EventSource ( Guid = "08D0D743-5C24-43F9-9723-98277CEA5F9B" ) ]
@@ -196,6 +256,15 @@ public void RaiseFunctionsMetricEvent(string executionId, ulong executionTimeSpa
196256 }
197257 }
198258
259+ [ Event ( 57907 , Level = EventLevel . Informational , Channel = EventChannel . Operational ) ]
260+ public void RaiseMetricsPerFunctionEvent ( string siteName , string functionName , ulong executionTimeInMs , ulong functionStartedCount , ulong functionCompletedCount , ulong functionFailedCount )
261+ {
262+ if ( IsEnabled ( ) )
263+ {
264+ WriteEvent ( 57907 , siteName , functionName , executionTimeInMs , functionStartedCount , functionCompletedCount , functionFailedCount ) ;
265+ }
266+ }
267+
199268 [ Event ( 57908 , Level = EventLevel . Informational , Channel = EventChannel . Operational ) ]
200269 public void RaiseFunctionsInfoEvent ( string siteName , string functionName , string inputBindings , string outputBindings , string scriptType , bool isDisabled )
201270 {
0 commit comments