Skip to content

Commit f54bfb6

Browse files
committed
(Shekhar) ETW Events Per Function execution
1 parent 37e9c02 commit f54bfb6

File tree

5 files changed

+146
-23
lines changed

5 files changed

+146
-23
lines changed

src/WebJobs.Script.WebHost/Diagnostics/MetricsEventManager.cs

Lines changed: 87 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,40 +2,49 @@
22
// Licensed under the MIT License. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Collections.Concurrent;
56
using System.Collections.Generic;
67
using System.Linq;
78
using System.Threading;
89
using System.Threading.Tasks;
910
using Microsoft.Azure.WebJobs.Script;
1011
using Microsoft.Azure.WebJobs.Script.Description;
12+
using Microsoft.Azure.WebJobs.Script.Diagnostics;
1113
using Microsoft.Diagnostics.Tracing;
14+
using WebJobs.Script.WebHost.Models;
1215

1316
namespace 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
{

src/WebJobs.Script.WebHost/Diagnostics/WebHostMetricsLogger.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,17 @@ public void BeginEvent(MetricEvent metricEvent)
1414
if (startedEvent != null)
1515
{
1616
startedEvent.StartTime = DateTime.Now;
17-
MetricsEventManager.FunctionStarted();
17+
MetricsEventManager.FunctionStarted(startedEvent);
1818
}
1919
}
2020

2121
public void EndEvent(MetricEvent metricEvent)
2222
{
23-
FunctionStartedEvent startedEvent = metricEvent as FunctionStartedEvent;
24-
if (startedEvent != null)
23+
FunctionStartedEvent completedEvent = metricEvent as FunctionStartedEvent;
24+
if (completedEvent != null)
2525
{
26-
startedEvent.EndTime = DateTime.Now;
27-
MetricsEventManager.FunctionCompleted();
26+
completedEvent.EndTime = DateTime.Now;
27+
MetricsEventManager.FunctionCompleted(completedEvent);
2828
}
2929
}
3030

src/WebJobs.Script.WebHost/GlobalSuppressions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@
2020
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Scope = "member", Target = "WebJobs.Script.WebHost.Models.FunctionStatus.#Errors")]
2121
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "WebJobs.Script.WebHost.App_Start.AutofacBootstrap.#Initialize(Autofac.ContainerBuilder,WebJobs.Script.WebHost.WebHostSettings)")]
2222
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Scope = "member", Target = "WebJobs.Script.WebHost.Models.HostStatus.#Errors")]
23+
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Scope = "member", Target = "WebJobs.Script.WebHost.Diagnostics.MetricsEventManager.#.cctor()")]
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
namespace WebJobs.Script.WebHost.Models
5+
{
6+
public enum ExecutionStage
7+
{
8+
Started,
9+
InProgress,
10+
Finished,
11+
Failed,
12+
Succeeded
13+
}
14+
15+
public class FunctionMetrics
16+
{
17+
private string _functionName;
18+
private ExecutionStage _executionStage;
19+
private long _executionTimeInMS;
20+
21+
public FunctionMetrics(string functionName, ExecutionStage executionStage, long executionTimeInMS)
22+
{
23+
_functionName = functionName;
24+
_executionStage = executionStage;
25+
_executionTimeInMS = executionTimeInMS;
26+
}
27+
28+
public string FunctionName
29+
{
30+
get
31+
{
32+
return _functionName;
33+
}
34+
}
35+
36+
public ExecutionStage ExecutionStage
37+
{
38+
get
39+
{
40+
return _executionStage;
41+
}
42+
}
43+
44+
public long ExecutionTimeInMS
45+
{
46+
get
47+
{
48+
return _executionTimeInMS;
49+
}
50+
}
51+
}
52+
}

src/WebJobs.Script.WebHost/WebJobs.Script.WebHost.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,7 @@
294294
<Compile Include="Handlers\EnsureHostRunningHandler.cs" />
295295
<Compile Include="HostSecrets.cs" />
296296
<Compile Include="Models\FunctionInvocation.cs" />
297+
<Compile Include="Models\FunctionMetrics.cs" />
297298
<Compile Include="Models\FunctionStatus.cs" />
298299
<Compile Include="Models\HostStatus.cs" />
299300
<Compile Include="Properties\AssemblyInfo.cs" />

0 commit comments

Comments
 (0)