Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions performance/SqlBindingBenchmarks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ public static void Main(string[] args)
{
BenchmarkRunner.Run<SqlTriggerBindingPerformance_Parallelization>();
}
if (runAll || args.Contains("trigger_changerate"))
{
BenchmarkRunner.Run<SqlTriggerBindingPerformance_ChangeRate>();
}
}
}
}
89 changes: 89 additions & 0 deletions performance/SqlTriggerBindingPerformance_ChangeRate.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System.Threading.Tasks;
using Microsoft.Azure.WebJobs.Extensions.Sql.Samples.TriggerBindingSamples;
using Microsoft.Azure.WebJobs.Extensions.Sql.Tests.Common;
using BenchmarkDotNet.Attributes;
using System.Threading;
using Microsoft.Azure.WebJobs.Extensions.Sql.Tests.Integration;
using System;

namespace Microsoft.Azure.WebJobs.Extensions.Sql.Performance
{
[MemoryDiagnoser]
public class SqlTriggerBindingPerformance_ChangeRate : IDisposable
{
private SqlTriggerBindingIntegrationTests TestObject;

[IterationSetup]
public void IterationSetup()
{
// We start a new instance each iteration because this test constantly inserts items
// which results in more items being inserted than the test looks for (intentionally).
// If we used the same trigger function for all runs then we run into issues with leftover
// items from previous runs being picked up by the new function - so to get around this
// we have to take the overhead hit of starting the function host each iteration to ensure
// a completely clean start for each iteration.
this.TestObject = new SqlTriggerBindingIntegrationTests();
this.TestObject.SetChangeTrackingForTable("Products", true);
this.TestObject.StartFunctionHost(nameof(ProductsTrigger), SupportedLanguages.CSharp);
}

[IterationCleanup]
public void IterationCleanup()
{
this.TestObject.Dispose();
}

/// <summary>
/// Runs a test with a high constant change rate. Items are inserted constantly until the test
/// detects that the specified number of items has been processed. Note this will NOT be all
/// of the items sent - those are being inserted at a much higher rate than the function can deal
/// with.
/// </summary>
/// <param name="count">The number of items to process before ending the test</param>
[Benchmark]
[Arguments(1000)]
public async Task ChangeRate(int count)
{
var tokenSource = new CancellationTokenSource();
await this.TestObject.WaitForProductChanges(
1,
count,
SqlChangeOperation.Insert,
() => { this.ChangesLoop(tokenSource.Token); return Task.CompletedTask; },
id => $"Product {id}",
id => id * 100,
this.TestObject.GetBatchProcessingTimeout(1, count)); // Wait for up to 30 seconds
tokenSource.Cancel();
}

private void ChangesLoop(CancellationToken token)
{
// Start off worker to insert items but then immediately return since
// we're only going to be watching for a subset of the inserted items
_ = Task.Run(() =>
{
const int ChangesInBatch = 50;
int startIndex = 1, endIndex = ChangesInBatch;
while (!token.IsCancellationRequested)
{
this.TestObject.InsertProducts(startIndex, endIndex);
startIndex += ChangesInBatch;
endIndex += ChangesInBatch;
// No specific reason for this delay except to avoid having this take
// up too many cycles when running the tests. 1 item/ms is fast enough
// for our purposes currently
Thread.Sleep(50);
}
}, token);
}

public void Dispose()
{
GC.SuppressFinalize(this);
this.TestObject.Dispose();
}
}
}
2 changes: 1 addition & 1 deletion test/Integration/SqlTriggerBindingIntegrationTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ void OutputHandler(object sender, DataReceivedEventArgs e)
/// <param name="batchSize">The batch size if different than the default batch size</param>
/// <param name="pollingIntervalMs">The polling interval in ms if different than the default polling interval</param>
/// <returns></returns>
protected int GetBatchProcessingTimeout(int firstId, int lastId, int batchSize = SqlTableChangeMonitor<object>.DefaultBatchSize, int pollingIntervalMs = SqlTableChangeMonitor<object>.DefaultPollingIntervalMs)
public int GetBatchProcessingTimeout(int firstId, int lastId, int batchSize = SqlTableChangeMonitor<object>.DefaultBatchSize, int pollingIntervalMs = SqlTableChangeMonitor<object>.DefaultPollingIntervalMs)
{
int changesToProcess = lastId - firstId + 1;
int calculatedTimeout = (int)(Math.Ceiling((double)changesToProcess / batchSize // The number of batches to process
Expand Down