Skip to content

Commit 2af6fc5

Browse files
Merge pull request #439 from Azure/chgagnon/perfFixes
Perf test fixes/improvements
2 parents 59762fe + 69d9634 commit 2af6fc5

File tree

6 files changed

+109
-40
lines changed

6 files changed

+109
-40
lines changed

performance/.editorconfig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# See https://github.com/dotnet/roslyn-analyzers/blob/main/.editorconfig for an example on different settings and how they're used
2+
3+
[*.cs]
4+
5+
# Disabled
6+
dotnet_diagnostic.CA1309.severity = silent # Use ordinal StringComparison - this isn't important for tests and just adds clutter
7+
dotnet_diagnostic.CA1305.severity = silent # Specify IFormatProvider - this isn't important for tests and just adds clutter
8+
dotnet_diagnostic.CA1707.severity = silent # Identifiers should not contain underscores - this helps make test names more readable
9+
dotnet_diagnostic.CA2201.severity = silent # Do not raise reserved exception types - tests can throw whatever they want

performance/SqlTriggerBindingPerformance.cs

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,16 @@
44
using System.Threading.Tasks;
55
using Microsoft.Azure.WebJobs.Extensions.Sql.Samples.TriggerBindingSamples;
66
using Microsoft.Azure.WebJobs.Extensions.Sql.Tests.Common;
7-
using Microsoft.Azure.WebJobs.Extensions.Sql.Tests.Integration;
87
using BenchmarkDotNet.Attributes;
98

109
namespace Microsoft.Azure.WebJobs.Extensions.Sql.Performance
1110
{
12-
public class SqlTriggerBindingPerformance : SqlTriggerBindingIntegrationTests
11+
[MemoryDiagnoser]
12+
public class SqlTriggerBindingPerformance : SqlTriggerBindingPerformanceTestBase
1313
{
1414
[GlobalSetup]
1515
public void GlobalSetup()
1616
{
17-
this.EnableChangeTrackingForTable("Products");
1817
this.StartFunctionHost(nameof(ProductsTrigger), SupportedLanguages.CSharp);
1918
}
2019

@@ -34,18 +33,5 @@ await this.WaitForProductChanges(
3433
id => id * 100,
3534
this.GetBatchProcessingTimeout(1, count));
3635
}
37-
38-
[IterationCleanup]
39-
public void IterationCleanup()
40-
{
41-
// Delete all rows in Products table after each iteration
42-
this.ExecuteNonQuery("TRUNCATE TABLE Products");
43-
}
44-
45-
[GlobalCleanup]
46-
public void GlobalCleanup()
47-
{
48-
this.Dispose();
49-
}
5036
}
5137
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
using Microsoft.Azure.WebJobs.Extensions.Sql.Tests.Integration;
5+
using BenchmarkDotNet.Attributes;
6+
7+
namespace Microsoft.Azure.WebJobs.Extensions.Sql.Performance
8+
{
9+
public class SqlTriggerBindingPerformanceTestBase : SqlTriggerBindingIntegrationTests
10+
{
11+
[IterationSetup]
12+
public void IterationSetup()
13+
{
14+
this.SetChangeTrackingForTable("Products", true);
15+
}
16+
17+
[IterationCleanup]
18+
public void IterationCleanup()
19+
{
20+
this.DisposeFunctionHosts();
21+
this.SetChangeTrackingForTable("Products", false);
22+
// Delete all rows in Products table after each iteration so we start fresh each time
23+
this.ExecuteNonQuery("TRUNCATE TABLE Products");
24+
// Delete the leases table, otherwise we may end up getting blocked by leases from a previous run
25+
this.ExecuteNonQuery(@"DECLARE @cmd varchar(100)
26+
DECLARE cmds CURSOR FOR
27+
SELECT 'DROP TABLE az_func.' + Name + ''
28+
FROM sys.tables
29+
WHERE Name LIKE 'Leases_%'
30+
31+
OPEN cmds
32+
WHILE 1 = 1
33+
BEGIN
34+
FETCH cmds INTO @cmd
35+
IF @@fetch_status != 0 BREAK
36+
EXEC(@cmd)
37+
END
38+
CLOSE cmds;
39+
DEALLOCATE cmds");
40+
}
41+
42+
[GlobalCleanup]
43+
public void GlobalCleanup()
44+
{
45+
this.Dispose();
46+
}
47+
}
48+
}

test/Integration/IntegrationTestBase.cs

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -394,18 +394,7 @@ public void Dispose()
394394
this.LogOutput($"Failed to close connection. Error: {e1.Message}");
395395
}
396396

397-
foreach (Process functionHost in this.FunctionHostList)
398-
{
399-
try
400-
{
401-
functionHost.Kill();
402-
functionHost.Dispose();
403-
}
404-
catch (Exception e2)
405-
{
406-
this.LogOutput($"Failed to stop function host, Error: {e2.Message}");
407-
}
408-
}
397+
this.DisposeFunctionHosts();
409398

410399
try
411400
{
@@ -432,6 +421,26 @@ public void Dispose()
432421
GC.SuppressFinalize(this);
433422
}
434423

424+
/// <summary>
425+
/// Disposes all the running function hosts
426+
/// </summary>
427+
protected void DisposeFunctionHosts()
428+
{
429+
foreach (Process functionHost in this.FunctionHostList)
430+
{
431+
try
432+
{
433+
functionHost.Kill();
434+
functionHost.Dispose();
435+
}
436+
catch (Exception ex)
437+
{
438+
this.LogOutput($"Failed to stop function host, Error: {ex.Message}");
439+
}
440+
}
441+
this.FunctionHostList.Clear();
442+
}
443+
435444
protected async Task<HttpResponseMessage> SendInputRequest(string functionName, string query = "")
436445
{
437446
string requestUri = $"http://localhost:{this.Port}/api/{functionName}/{query}";

test/Integration/SqlTriggerBindingIntegrationTests.cs

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public SqlTriggerBindingIntegrationTests(ITestOutputHelper output = null) : base
2929
[Fact]
3030
public async Task SingleOperationTriggerTest()
3131
{
32-
this.EnableChangeTrackingForTable("Products");
32+
this.SetChangeTrackingForTable("Products");
3333
this.StartFunctionHost(nameof(ProductsTrigger), SupportedLanguages.CSharp);
3434

3535
int firstId = 1;
@@ -82,7 +82,7 @@ public async Task BatchSizeOverrideTriggerTest()
8282
const int batchSize = SqlTableChangeMonitor<object>.DefaultBatchSize * 4;
8383
const int firstId = 1;
8484
const int lastId = batchSize;
85-
this.EnableChangeTrackingForTable("Products");
85+
this.SetChangeTrackingForTable("Products");
8686
var taskCompletionSource = new TaskCompletionSource<bool>();
8787
DataReceivedEventHandler handler = TestUtils.CreateOutputReceievedHandler(
8888
taskCompletionSource,
@@ -120,10 +120,10 @@ public async Task PollingIntervalOverrideTriggerTest()
120120
const int firstId = 1;
121121
// Use enough items to require 5 batches to be processed - the test will
122122
// only wait for the expected time and timeout if the default polling
123-
// interval isn't actually modified.
123+
// interval isn't actually modified.
124124
const int lastId = SqlTableChangeMonitor<object>.DefaultBatchSize * 5;
125125
const int pollingIntervalMs = SqlTableChangeMonitor<object>.DefaultPollingIntervalMs / 2;
126-
this.EnableChangeTrackingForTable("Products");
126+
this.SetChangeTrackingForTable("Products");
127127
var taskCompletionSource = new TaskCompletionSource<bool>();
128128
DataReceivedEventHandler handler = TestUtils.CreateOutputReceievedHandler(
129129
taskCompletionSource,
@@ -160,7 +160,7 @@ public async Task MultiOperationTriggerTest()
160160
{
161161
int firstId = 1;
162162
int lastId = 5;
163-
this.EnableChangeTrackingForTable("Products");
163+
this.SetChangeTrackingForTable("Products");
164164
this.StartFunctionHost(nameof(ProductsTrigger), SupportedLanguages.CSharp);
165165

166166
// 1. Insert + multiple updates to a row are treated as single insert with latest row values.
@@ -241,7 +241,7 @@ public async Task MultiFunctionTriggerTest()
241241
const string Trigger1Changes = "Trigger1 Changes: ";
242242
const string Trigger2Changes = "Trigger2 Changes: ";
243243

244-
this.EnableChangeTrackingForTable("Products");
244+
this.SetChangeTrackingForTable("Products");
245245

246246
string functionList = $"{nameof(MultiFunctionTrigger.MultiFunctionTrigger1)} {nameof(MultiFunctionTrigger.MultiFunctionTrigger2)}";
247247
this.StartFunctionHost(functionList, SupportedLanguages.CSharp, useTestFolder: true);
@@ -363,7 +363,7 @@ public async Task MultiFunctionTriggerTest()
363363
[Fact]
364364
public async Task MultiHostTriggerTest()
365365
{
366-
this.EnableChangeTrackingForTable("Products");
366+
this.SetChangeTrackingForTable("Products");
367367

368368
// Prepare three function host processes.
369369
this.StartFunctionHost(nameof(ProductsTrigger), SupportedLanguages.CSharp);
@@ -479,11 +479,11 @@ ALTER DATABASE [{this.DatabaseName}]
479479
");
480480
}
481481

482-
protected void EnableChangeTrackingForTable(string tableName)
482+
protected void SetChangeTrackingForTable(string tableName, bool enable = true)
483483
{
484484
this.ExecuteNonQuery($@"
485485
ALTER TABLE [dbo].[{tableName}]
486-
ENABLE CHANGE_TRACKING;
486+
{(enable ? "ENABLE" : "DISABLE")} CHANGE_TRACKING;
487487
");
488488
}
489489

@@ -559,6 +559,7 @@ void MonitorOutputData(object sender, DataReceivedEventArgs e)
559559
await actions();
560560

561561
// Now wait until either we timeout or we've gotten all the expected changes, whichever comes first
562+
Console.WriteLine("Waiting for product changes");
562563
await taskCompletion.Task.TimeoutAfter(TimeSpan.FromMilliseconds(timeoutMs), $"Timed out waiting for {operation} changes.");
563564

564565
// Unhook handler since we're done monitoring these changes so we aren't checking other changes done later
@@ -610,7 +611,7 @@ void OutputHandler(object sender, DataReceivedEventArgs e)
610611

611612
/// <summary>
612613
/// Gets a timeout value to use when processing the given number of changes, based on the
613-
/// default batch size and polling interval.
614+
/// default batch size and polling interval.
614615
/// </summary>
615616
/// <param name="firstId">The first ID in the batch to process</param>
616617
/// <param name="lastId">The last ID in the batch to process</param>

test/README.md

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ Our integration tests are based on functions from the samples project. To run in
5656
this.StartFunctionHost(nameof(<FUNCTIONNAME>), lang); // Replace <FUNCTIONNAME> with the class name of the function this test is running against
5757
// test code here
5858
}
59-
```
59+
```
6060
Ex: When the test has parameters:
6161
6262
```
@@ -86,4 +86,20 @@ Our integration tests are based on functions from the samples project. To run in
8686
this.StartFunctionHost(nameof(<FUNCTIONNAME>), lang); // Replace <FUNCTIONNAME> with the class name of the function this test is running against
8787
// test code here
8888
}
89-
```
89+
```
90+
91+
## Troubleshooting Tests
92+
93+
This section lists some things to try to help troubleshoot test failures
94+
95+
### Enable debug logging on the Function
96+
97+
Enabling debug logging can greatly increase the information available which can help track down issues or understand at least where the problem may be. To enable debug logging for the Function open [host.json](../samples/samples-csharp/host.json) and add the following property to the `logLevel` section, then rebuild and re-run your test.
98+
99+
```json
100+
"logLevel": {
101+
"default": "Debug"
102+
}
103+
```
104+
105+
WARNING : Doing this will add a not-insignificant overhead to the test run duration from writing all the additional content to the log files, which may cause timeouts to occur in tests. If this happens you can temporarily increase those timeouts while debug logging is enabled to avoid having unexpected failures.

0 commit comments

Comments
 (0)