Skip to content

Commit 5f1f984

Browse files
committed
Fixing issue with incorrectly scoped stopwatch
1 parent 0d3656c commit 5f1f984

File tree

2 files changed

+64
-21
lines changed

2 files changed

+64
-21
lines changed

src/WebJobs.Script/Description/FunctionInvokerBase.cs

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ namespace Microsoft.Azure.WebJobs.Script.Description
2323
{
2424
public abstract class FunctionInvokerBase : IFunctionInvoker, IDisposable
2525
{
26-
private readonly Stopwatch _stopwatch = new Stopwatch();
2726
private bool _disposed = false;
2827
private IMetricsLogger _metrics;
2928
private IDisposable _fileChangeSubscription;
@@ -126,7 +125,8 @@ public async Task Invoke(object[] parameters)
126125
var startedEvent = new FunctionStartedEvent(functionExecutionContext.InvocationId, Metadata);
127126
_metrics.BeginEvent(startedEvent);
128127
var invokeLatencyEvent = LogInvocationMetrics(_metrics, Metadata);
129-
_stopwatch.Restart();
128+
var invocationStopWatch = new Stopwatch();
129+
invocationStopWatch.Start();
130130

131131
try
132132
{
@@ -144,11 +144,8 @@ public async Task Invoke(object[] parameters)
144144

145145
await InvokeCore(parameters, context);
146146

147-
_stopwatch.Stop();
148-
149-
string completeMessage = $"Function completed (Success, Id={invocationId}, Duration={_stopwatch.ElapsedMilliseconds}ms)";
150-
TraceWriter.Info(completeMessage);
151-
Logger?.LogInformation(completeMessage);
147+
invocationStopWatch.Stop();
148+
LogFunctionResult(startedEvent, true, invocationId, invocationStopWatch.ElapsedMilliseconds);
152149
}
153150
catch (AggregateException ex)
154151
{
@@ -165,14 +162,14 @@ public async Task Invoke(object[] parameters)
165162
exInfo = ExceptionDispatchInfo.Capture(ex);
166163
}
167164

168-
_stopwatch.Stop();
169-
LogFunctionFailed(startedEvent, "Failure", invocationId, _stopwatch.ElapsedMilliseconds);
165+
invocationStopWatch.Stop();
166+
LogFunctionResult(startedEvent, false, invocationId, invocationStopWatch.ElapsedMilliseconds);
170167
exInfo.Throw();
171168
}
172169
catch
173170
{
174-
_stopwatch.Stop();
175-
LogFunctionFailed(startedEvent, "Failure", invocationId, _stopwatch.ElapsedMilliseconds);
171+
invocationStopWatch.Stop();
172+
LogFunctionResult(startedEvent, false, invocationId, invocationStopWatch.ElapsedMilliseconds);
176173
throw;
177174
}
178175
finally
@@ -202,16 +199,21 @@ internal static object LogInvocationMetrics(IMetricsLogger metrics, FunctionMeta
202199
return metrics.BeginEvent(MetricEventNames.FunctionInvokeLatency, metadata.Name);
203200
}
204201

205-
private void LogFunctionFailed(FunctionStartedEvent startedEvent, string resultString, string invocationId, long elapsedMs)
202+
private void LogFunctionResult(FunctionStartedEvent startedEvent, bool success, string invocationId, long elapsedMs)
206203
{
207204
if (startedEvent != null)
208205
{
209-
startedEvent.Success = false;
206+
startedEvent.Success = success;
210207
}
211208

209+
string resultString = success ? "Success" : "Failure";
212210
string message = $"Function completed ({resultString}, Id={invocationId ?? "0"}, Duration={elapsedMs}ms)";
213-
TraceWriter.Error(message);
214-
Logger?.LogError(message);
211+
212+
TraceLevel traceWriterLevel = success ? TraceLevel.Info : TraceLevel.Error;
213+
LogLevel logLevel = success ? LogLevel.Information : LogLevel.Error;
214+
215+
TraceWriter.Trace(message, traceWriterLevel, null);
216+
Logger?.Log(logLevel, new EventId(0), message, null, (s, e) => s);
215217
}
216218

217219
protected TraceWriter CreateUserTraceWriter(TraceWriter traceWriter)

test/WebJobs.Script.Tests/Description/FunctionInvokerBaseTests.cs

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,15 +94,48 @@ public async Task Invoke_Success_EmitsExpectedEvents()
9494
Assert.Equal(startLatencyEvent, completedLatencyEvent);
9595
}
9696

97+
[Fact]
98+
public async Task Invoke_EmitsExpectedDuration()
99+
{
100+
var executionContext = new ExecutionContext
101+
{
102+
InvocationId = Guid.NewGuid()
103+
};
104+
var parameters1 = new object[] { "Test", _traceWriter, executionContext, new InvocationData { Delay = 2000 } };
105+
var parameters2 = new object[] { "Test", _traceWriter, executionContext };
106+
107+
Task invocation1 = _invoker.Invoke(parameters1);
108+
Task invocation2 = _invoker.Invoke(parameters2);
109+
110+
await Task.WhenAll(invocation1, invocation2);
111+
112+
Assert.Equal(2, _metricsLogger.MetricEventsBegan.Count);
113+
Assert.Equal(2, _metricsLogger.EventsBegan.Count);
114+
Assert.Equal(2, _metricsLogger.MetricEventsEnded.Count);
115+
Assert.Equal(2, _metricsLogger.EventsEnded.Count);
116+
117+
var completionEvents = _traceWriter.Traces
118+
.Select(e => Regex.Match(e.Message, $"Function completed \\(Success, Id={executionContext.InvocationId}, Duration=(?'duration'[0-9]*)ms\\)"))
119+
.Where(m => m.Success)
120+
.ToList();
121+
122+
Assert.Equal(2, completionEvents.Count);
123+
int invocation1Duration = (int.Parse(completionEvents[1].Groups["duration"].Value) / 100) * 100;
124+
int invocation2Duration = (int.Parse(completionEvents[0].Groups["duration"].Value) / 100) * 100;
125+
126+
Assert.NotEqual(invocation1Duration, invocation2Duration);
127+
Assert.Equal(2000, invocation1Duration);
128+
Assert.Equal(500, invocation2Duration);
129+
}
130+
97131
[Fact]
98132
public async Task Invoke_Failure_EmitsExpectedEvents()
99133
{
100134
var executionContext = new ExecutionContext
101135
{
102136
InvocationId = Guid.NewGuid()
103137
};
104-
var parameters = new object[] { "Test", _traceWriter, executionContext };
105-
_invoker.Throw = true;
138+
var parameters = new object[] { "Test", _traceWriter, executionContext, new InvocationData { Throw = true } };
106139
await Assert.ThrowsAsync<InvalidOperationException>(async () =>
107140
{
108141
await _invoker.Invoke(parameters);
@@ -136,16 +169,24 @@ public MockInvoker(ScriptHost host, FunctionMetadata metadata, ITraceWriterFacto
136169
{
137170
}
138171

139-
public bool Throw { get; set; }
140-
141172
protected override async Task InvokeCore(object[] parameters, FunctionInvocationContext context)
142173
{
143-
if (Throw)
174+
InvocationData invocation = parameters.OfType<InvocationData>().FirstOrDefault() ?? new InvocationData();
175+
176+
if (invocation.Throw)
144177
{
145178
throw new InvalidOperationException("Kaboom!");
146179
}
147-
await Task.Delay(500);
180+
181+
await Task.Delay(invocation.Delay);
148182
}
149183
}
184+
185+
private class InvocationData
186+
{
187+
public int Delay { get; set; } = 500;
188+
189+
public bool Throw { get; set; }
190+
}
150191
}
151192
}

0 commit comments

Comments
 (0)