Skip to content

Commit 04b0a8d

Browse files
authored
Tidy up performance tests (#3230)
* User Story 34145: Tidy up Bulk Copy unmatched column name work - Tidied up the existing Performance benchmarks. - Added environment variables to specify config files. - Removed Newtonsoft.Json as a dependency. - Simplified csproj a bit. - Improved benchmark running instructions.
1 parent 1247ca4 commit 04b0a8d

File tree

8 files changed

+265
-53
lines changed

8 files changed

+265
-53
lines changed

BUILDGUIDE.md

Lines changed: 110 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -339,14 +339,118 @@ dotnet test <test_properties...> --collect:"XPlat Code Coverage"
339339

340340
## Run Performance Tests
341341

342-
### Running Performance test project directly
342+
The performance tests live here:
343+
`src\Microsoft.Data.SqlClient\tests\PerformanceTests\`
343344

344-
Project location from Root: `src\Microsoft.Data.SqlClient\tests\PerformanceTests\Microsoft.Data.SqlClient.PerformanceTests.csproj`
345-
Configure `runnerconfig.json` file with connection string and preferred settings to run Benchmark Jobs.
345+
They can be run from the command line by following the instructions below.
346+
347+
Launch a shell and change into the project directory:
348+
349+
PowerShell:
350+
351+
```pwsh
352+
> cd src\Microsoft.Data.SqlClient\tests\PerformanceTests
353+
```
354+
355+
Bash:
356+
357+
```bash
358+
$ cd src/Microsoft.Data.SqlClient/tests/PerformanceTests
359+
```
360+
361+
### Create Database
362+
363+
Create an empty database for the benchmarks to use. This example assumes
364+
a local SQL server instance using SQL authentication:
346365

347366
```bash
348-
cd src\Microsoft.Data.SqlClient\tests\PerformanceTests
349-
dotnet run -c Release -f net8.0
367+
$ sqlcmd -S localhost -U sa -P password
368+
1> create database [sqlclient-perf-db]
369+
2> go
370+
1> quit
371+
```
372+
373+
The default `runnerconfig.json` expects a database named `sqlclient-perf-db`,
374+
but you may change the config to use any existing database. All tables in
375+
the database will be dropped when running the benchmarks.
376+
377+
### Configure Runner
378+
379+
Configure the benchmarks by editing the `runnerconfig.json` file directly in the
380+
`PerformanceTests` directory with an appropriate connection string and benchmark
381+
settings:
382+
383+
```json
384+
{
385+
"ConnectionString": "Server=tcp:localhost; Integrated Security=true; Initial Catalog=sqlclient-perf-db;",
386+
"UseManagedSniOnWindows": false,
387+
"Benchmarks":
388+
{
389+
"SqlConnectionRunnerConfig":
390+
{
391+
"Enabled": true,
392+
"LaunchCount": 1,
393+
"IterationCount": 50,
394+
"InvocationCount":30,
395+
"WarmupCount": 5,
396+
"RowCount": 0
397+
},
398+
...
399+
}
400+
}
350401
```
351402

352-
_Only "**Release** Configuration" applies to Performance Tests_
403+
Individual benchmarks may be enabled or disabled, and each has several
404+
benchmarking options for fine tuning.
405+
406+
After making edits to `runnerconfig.json` you must perform a build which will
407+
copy the file into the `artifacts` directory alongside the benchmark DLL. By
408+
default, the benchmarks look for `runnerconfig.json` in the same directory as
409+
the DLL.
410+
411+
Optionally, to avoid polluting your git workspace and requring a build after
412+
each config change, copy `runnerconfig.json` to a new file, make your edits
413+
there, and then specify the new file with the RUNNER_CONFIG environment
414+
variable.
415+
416+
PowerShell:
417+
418+
```pwsh
419+
> copy runnerconfig.json $HOME\.configs\runnerconfig.json
420+
421+
# Make edits to $HOME\.configs\runnerconfig.json
422+
423+
# You must set the RUNNER_CONFIG environment variable for the current shell.
424+
> $env:RUNNER_CONFIG="${HOME}\.configs\runnerconfig.json"
425+
```
426+
427+
Bash:
428+
429+
```bash
430+
$ cp runnerconfig.json ~/.configs/runnerconfig.json
431+
432+
# Make edits to ~/.configs/runnerconfig.json
433+
434+
# Optionally export RUNNER_CONFIG.
435+
$ export RUNNER_CONFIG=~/.configs/runnerconfig.json
436+
```
437+
438+
### Run Benchmarks
439+
440+
All benchmarks must be compiled and run in **Release** configuration.
441+
442+
PowerShell:
443+
444+
```pwsh
445+
> dotnet run -c Release -f net9.0
446+
```
447+
448+
Bash:
449+
450+
```bash
451+
# Omit RUNNER_CONFIG if you exported it earlier, or if you're using the
452+
# copy prepared by the build.
453+
$ dotnet run -c Release -f net9.0
454+
455+
$ RUNNER_CONFIG=~/.configs/runnerconfig.json dotnet run -c Release -f net9.0
456+
```

src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/BaseRunner.cs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,14 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5-
using System.IO;
6-
using Newtonsoft.Json;
7-
85
namespace Microsoft.Data.SqlClient.PerformanceTests
96
{
107
public abstract class BaseRunner
118
{
129
public BaseRunner()
1310
{
14-
s_config = JsonConvert.DeserializeObject<Config>(File.ReadAllText("runnerconfig.json"));
15-
s_datatypes = JsonConvert.DeserializeObject<DataTypes>(File.ReadAllText("datatypes.json"));
11+
s_config = Config.Load();
12+
s_datatypes = DataTypes.Load();
1613
}
1714

1815
internal static Config s_config;

src/Microsoft.Data.SqlClient/tests/PerformanceTests/Config/Config.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,28 @@ public class Config
1212
public string ConnectionString;
1313
public bool UseManagedSniOnWindows;
1414
public Benchmarks Benchmarks;
15+
16+
/// <summary>
17+
/// Load the benchmark configuration from a JSON file.
18+
///
19+
/// If the environment variable "RUNNER_CONFIG" is set, it will be used
20+
/// as the path to the config file. Otherwise, the file
21+
/// "runnerconfig.json" in the current working directory will be used.
22+
/// </summary>
23+
///
24+
/// <returns>
25+
/// The Config instance populated from the JSON config file.
26+
/// </returns>
27+
///
28+
/// <exception cref="InvalidOperationException">
29+
/// Thrown if the config file cannot be read or deserialized.
30+
/// </exception>
31+
///
32+
public static Config Load()
33+
{
34+
return Loader.FromJsonFile<Config>(
35+
"runnerconfig.json", "RUNNER_CONFIG");
36+
}
1537
}
1638

1739
public class Benchmarks

src/Microsoft.Data.SqlClient/tests/PerformanceTests/Config/DataTypes.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,28 @@ public class DataTypes
1818
public MaxLengthBinaryType[] Binary;
1919
public MaxLengthValueLengthType[] MaxTypes;
2020
public DataType[] Others;
21+
22+
/// <summary>
23+
/// Load the data types configuration from a JSON file.
24+
///
25+
/// If the environment variable "DATATYPES_CONFIG" is set, it will be
26+
/// used as the path to the config file. Otherwise, the file
27+
/// "datatypes.json" in the current working directory will be used.
28+
/// </summary>
29+
///
30+
/// <returns>
31+
/// The DataTypes instance populated from the JSON file.
32+
/// </returns>
33+
///
34+
/// <exception cref="InvalidOperationException">
35+
/// Thrown if the config file cannot be read or deserialized.
36+
/// </exception>
37+
///
38+
public static DataTypes Load()
39+
{
40+
return Loader.FromJsonFile<DataTypes>(
41+
"datatypes.json", "DATATYPES_CONFIG");
42+
}
2143
}
2244

2345
/// <summary>
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.IO;
7+
using System.Text.Json;
8+
9+
namespace Microsoft.Data.SqlClient.PerformanceTests
10+
{
11+
public static class Loader
12+
{
13+
/// <summary>
14+
/// Load a JSON config file into the given type.
15+
/// </summary>
16+
///
17+
/// <typeparam name="T">
18+
/// The type to deserialize the JSON into.
19+
/// </typeparam>
20+
///
21+
/// <param name="path">
22+
/// The path to the JSON config file.
23+
/// </param>
24+
///
25+
/// <param name="envOverride">
26+
/// An optional environment variable that, if set, will be used as
27+
/// the config file path, ignoring path.
28+
/// </param>
29+
///
30+
/// <returns>
31+
/// The T instance populated from the JSON config file.
32+
/// </returns>
33+
///
34+
/// <exception cref="InvalidOperationException">
35+
/// Thrown if the config file cannot be read or deserialized.
36+
/// </exception>
37+
///
38+
public static T FromJsonFile<T>(
39+
string path,
40+
string envOverride = null)
41+
where T : class
42+
{
43+
string configFile =
44+
envOverride is null
45+
? path
46+
: Environment.GetEnvironmentVariable(envOverride)
47+
?? path;
48+
49+
T config = null;
50+
Exception error = null;
51+
try
52+
{
53+
using var stream = File.OpenRead(configFile);
54+
config =
55+
JsonSerializer.Deserialize<T>(
56+
stream,
57+
new JsonSerializerOptions
58+
{
59+
IncludeFields = true,
60+
ReadCommentHandling = JsonCommentHandling.Skip
61+
});
62+
}
63+
catch (Exception ex)
64+
{
65+
error = ex;
66+
}
67+
68+
if (config is null || error is not null)
69+
{
70+
throw new InvalidOperationException(
71+
$"Failed to load {typeof(T).Name} config from file=" +
72+
$"{configFile}", error);
73+
}
74+
75+
return config;
76+
}
77+
}
78+
}

src/Microsoft.Data.SqlClient/tests/PerformanceTests/Microsoft.Data.SqlClient.PerformanceTests.csproj

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,46 +3,31 @@
33
<OutputType>Exe</OutputType>
44
<AssemblyName>PerformanceTests</AssemblyName>
55
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
6-
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
76
<Configurations>Debug;Release;</Configurations>
87
<IntermediateOutputPath>$(ObjFolder)$(Configuration).$(Platform).$(AssemblyName)</IntermediateOutputPath>
98
<OutputPath>$(BinFolder)$(Configuration).$(Platform).$(AssemblyName)</OutputPath>
109
<StartupObject>Microsoft.Data.SqlClient.PerformanceTests.Program</StartupObject>
1110
</PropertyGroup>
11+
1212
<ItemGroup>
1313
<EmbeddedResource Remove="BenchmarkDotNet.Artifacts\**" />
1414
<None Remove="BenchmarkDotNet.Artifacts\**" />
1515
</ItemGroup>
16+
1617
<ItemGroup>
1718
<None Remove=".AssemblyAttributes" />
1819
</ItemGroup>
1920

2021
<ItemGroup>
2122
<ProjectReference Include="$(NetCoreSource)src\Microsoft.Data.SqlClient.csproj" />
2223
</ItemGroup>
24+
2325
<ItemGroup>
2426
<PackageReference Include="BenchmarkDotNet" Version="$(BenchmarkDotNetVersion)" />
25-
<PackageReference Include="Newtonsoft.Json" Version="$(NewtonsoftJsonVersion)" />
2627
</ItemGroup>
28+
2729
<ItemGroup>
2830
<None Include="datatypes.json" CopyToOutputDirectory="PreserveNewest" />
2931
<None Include="runnerconfig.json" CopyToOutputDirectory="PreserveNewest" />
3032
</ItemGroup>
31-
<ItemGroup>
32-
<Compile Include="Config\Config.cs" />
33-
<Compile Include="Config\Constants.cs" />
34-
<Compile Include="Config\DataTypes.cs" />
35-
<Compile Include="Config\BenchmarkConfig.cs" />
36-
<Compile Include="DBFramework\Table.cs" />
37-
<Compile Include="DBFramework\Column.cs" />
38-
<Compile Include="DBFramework\DbUtils.cs" />
39-
<Compile Include="DBFramework\TablePatterns.cs" />
40-
<Compile Include="BenchmarkRunners\BaseRunner.cs" />
41-
<Compile Include="BenchmarkRunners\SqlBulkCopyRunner.cs" />
42-
<Compile Include="BenchmarkRunners\SqlConnectionRunner.cs" />
43-
<Compile Include="BenchmarkRunners\SqlCommandRunner.cs" />
44-
<Compile Include="BenchmarkRunners\DataTypeReaderRunner.cs" />
45-
<Compile Include="BenchmarkRunners\DataTypeReaderAsyncRunner.cs" />
46-
<Compile Include="Program.cs" />
47-
</ItemGroup>
4833
</Project>

0 commit comments

Comments
 (0)