Skip to content
Open
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
2 changes: 1 addition & 1 deletion BUILDGUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,7 @@ The top-level flags control global runner behavior:
| `WaitForProfiler` | Pauses at startup and prints the process ID so you can attach an external profiler (e.g. `dotnet-trace`) before benchmarks run. |
| `UseNativeMemoryAndETWProfiler` | Attaches the `NativeMemoryProfiler` and `EtwProfiler` BenchmarkDotNet diagnosers. Windows only; has no effect on other OSes. |

Some benchmarks (e.g. `DataTypeReaderRunner`, `DataTypeReaderAsyncRunner`) also
Some benchmarks (e.g. `DataTypeReaderRunner`) also
read per-type test values from `datatypes.json` in the `PerformanceTests`
directory. Like `runnerconfig.jsonc`, this file's location can be overridden
with the `DATATYPES_CONFIG` environment variable.
Expand Down
2 changes: 1 addition & 1 deletion eng/pipelines/perf/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ supplies the isolated dedicated host, the tuned SQL instance, and the disjoint c
| Client CPU pin | Pins the benchmark process to `PERF_CLIENT_CPUS` (`taskset` on Linux, `ProcessorAffinity` on Windows). |
| Fail loud | Preflight `SELECT 1` before any pass, **and** a post-pass guard that fails the run if a pass produced **zero** benchmark results — so an empty comparison can never be reported green. |
| Warm-up | Touches the target DB in the preflight to warm the buffer pool / plan cache before the first measured benchmark. |
| Allocator tuning (Linux) | Exports `MALLOC_MMAP_THRESHOLD_=128MiB` and `MALLOC_TRIM_THRESHOLD_=-1` so large-buffer benches (`AsyncLargeDataRead`, `SqlBulkCopy`) stop re-`mmap`ing per iteration. |
| Allocator tuning (Linux) | Exports `MALLOC_MMAP_THRESHOLD_=128MiB` and `MALLOC_TRIM_THRESHOLD_=-1` so large-buffer benches (`LargeDataRead`, `SqlBulkCopy`) stop re-`mmap`ing per iteration. |
| Network tuning (Linux) | Best-effort `sysctl` to widen the ephemeral port range and enable `tcp_tw_reuse` for churn benches (`ConnectionPoolStress`, `ParallelAsyncConnection`). Never fails the run. |
| Diagnostics | Writes `results/diagnostics/`: SQL instance config (MAXDOP, memory, affinity, tempdb files, `@@VERSION`), host CPU topology, and per-pass CPU-clock/thermal telemetry (before/after each pass). |
| Regression gate | `failOnRegression` threads `--fail-on-regression`; only a **candidate-slower** delta past the threshold fails, and in interleaved mode only after best-of-N confirmation. Default off. |
Expand Down
2 changes: 1 addition & 1 deletion eng/pipelines/perf/scripts/run-perf-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ DIAG_DIR="${RESULTS_DIR}/diagnostics"
mkdir -p "${DIAG_DIR}"

# --- §2.8 Allocator tuning (exported so the 'dotnet run' children inherit it) ---------------------
# Large-buffer benches (AsyncLargeDataRead, SqlBulkCopy) re-mmap a big buffer every iteration under
# Large-buffer benches (LargeDataRead, SqlBulkCopy) re-mmap a big buffer every iteration under
# glibc malloc; keep those allocations on the heap and stop trimming freed pages so they are reused,
# which removes a major source of per-iteration variance.
export MALLOC_MMAP_THRESHOLD_="${MALLOC_MMAP_THRESHOLD_:-134217728}" # 128 MiB
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,17 @@ public ColumnMasterKeyCertificateFixture()

public X509Certificate2? ColumnMasterKeyCertificate { get; }

public string? ColumnMasterKeyCertificatePath { get; }

protected ColumnMasterKeyCertificateFixture(bool createCertificate)
{
if (createCertificate)
{
ColumnMasterKeyCertificate = CreateCertificate(nameof(ColumnMasterKeyCertificate), Array.Empty<string>(), Array.Empty<string>());

AddToStore(ColumnMasterKeyCertificate, StoreLocation.CurrentUser, StoreName.My);

ColumnMasterKeyCertificatePath = $"{StoreLocation.CurrentUser}/{StoreName.My}/{ColumnMasterKeyCertificate.Thumbprint}";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Security.Cryptography;

namespace Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects;

/// <summary>
/// A column encryption key, created at the start of its scope and dropped when disposed.
/// </summary>
public sealed class ColumnEncryptionKey : DatabaseObject<ColumnMasterKey>
{
private const int PlaintextKeyLength = 32;

private const string DefinitionTemplate = "CREATE COLUMN ENCRYPTION KEY {0} WITH VALUES" +
" (COLUMN_MASTER_KEY = {1}, ALGORITHM = 'RSA_OAEP', ENCRYPTED_VALUE = 0x{2})";

private ColumnMasterKey ColumnMasterKey => State;

/// <summary>
/// Initializes a new instance of the ColumnEncryptionKey class using the specified SQL connection,
/// name and a column master key.
/// </summary>
/// <param name="connection">The SQL connection used to interact with the database.</param>
/// <param name="namePrefix">The column encryption key name.</param>
/// <param name="cmkOrigin">The column master key which backs this encryption key.</param>
public ColumnEncryptionKey(SqlConnection connection, string namePrefix, ColumnMasterKey cmkOrigin)
: base(connection, GenerateLongName(namePrefix), definition: DefinitionTemplate,
state: cmkOrigin, shouldCreate: true, shouldDrop: true)
{
}

protected override void CreateObject(string definition)
{
string encryptedValue;

using (RandomNumberGenerator rnd = RandomNumberGenerator.Create())
{
byte[] randomPlaintext = new byte[PlaintextKeyLength];
byte[] encryptedPlaintext;

rnd.GetBytes(randomPlaintext);
encryptedPlaintext = ColumnMasterKey.Encrypt(randomPlaintext);

encryptedValue = BitConverter.ToString(encryptedPlaintext).Replace("-", "");
}

definition = string.Format(definition, Name, ColumnMasterKey.Name, encryptedValue);
using SqlCommand createCommand = new(definition, Connection);

createCommand.ExecuteNonQuery();
}

protected override void DropObject()
{
using SqlCommand dropCommand = new($"IF EXISTS (SELECT 1 FROM sys.column_encryption_keys where name = @Name) DROP COLUMN ENCRYPTION KEY {Name}", Connection);
dropCommand.Parameters.AddWithValue("@Name", UnescapedName);

dropCommand.ExecuteNonQuery();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Text;

namespace Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects;

/// <summary>
/// A column master key, created at the start of its scope and dropped when disposed.
/// </summary>
public abstract class ColumnMasterKey : DatabaseObject<ColumnMasterKey.CreationParameters>
{
private const string DefinitionTemplate = "CREATE COLUMN MASTER KEY {0} WITH (KEY_STORE_PROVIDER_NAME = '{1}', KEY_PATH = '{2}'{3})";

public sealed class CreationParameters
{
public SqlColumnEncryptionKeyStoreProvider Provider { get; }

public string ProviderName { get; }

public string KeyPath { get; }

public bool AllowEnclaveComputations { get; }

internal CreationParameters(SqlColumnEncryptionKeyStoreProvider provider,
string providerName,
string keyPath,
bool allowEnclaveComputations)
{
Provider = provider;
ProviderName = providerName;
KeyPath = keyPath;
AllowEnclaveComputations = allowEnclaveComputations;
}
}

protected ColumnMasterKey(SqlConnection connection, string namePrefix, CreationParameters creationParameters)
: base(connection, name: GenerateLongName(namePrefix), definition: DefinitionTemplate,
state: creationParameters, shouldCreate: true, shouldDrop: true)
{
}

protected override void CreateObject(string definition)
{
string enclaveStatement;

if (State.AllowEnclaveComputations)
{
byte[] signature = State.Provider.SignColumnMasterKeyMetadata(State.KeyPath, State.AllowEnclaveComputations);
string signatureString = BitConverter.ToString(signature).Replace("-", "");

enclaveStatement = ", ENCLAVE_COMPUTATIONS (SIGNATURE = 0x" + signatureString + ")";
}
else
{
enclaveStatement = string.Empty;
}

definition = string.Format(definition, Name, State.ProviderName, State.KeyPath, enclaveStatement);

using SqlCommand createCommand = new(definition, Connection);

createCommand.ExecuteNonQuery();
}

protected override void DropObject()
{
using SqlCommand dropCommand = new($"IF EXISTS (SELECT 1 FROM sys.column_master_keys where name = @Name) DROP COLUMN MASTER KEY {Name}", Connection);
dropCommand.Parameters.AddWithValue("@Name", UnescapedName);

dropCommand.ExecuteNonQuery();
}

public byte[] Encrypt(byte[] columnEncryptionKey) =>
State.Provider.EncryptColumnEncryptionKey(State.KeyPath, "RSA_OAEP", columnEncryptionKey);

public byte[] Decrypt(byte[] encryptedColumnEncryptionKey) =>
State.Provider.DecryptColumnEncryptionKey(State.KeyPath, "RSA_OAEP", encryptedColumnEncryptionKey);
}

/// <summary>
/// A column master key backed by a Cryptographic Service Provider. Created at the start of its
/// scope and dropped when disposed.
/// </summary>
public sealed class CspProviderBackedColumnMasterKey : ColumnMasterKey
{
/// <summary>
/// Initializes a new instance of the CspProviderBackedColumnMasterKey class using the specified
/// SQL connection, name and a certificate containing a CSP-backed private key.
/// </summary>
/// <remarks>
/// <para>
/// If a column master key with the specified name already exists, it will be dropped automatically
/// before creation.
/// </para>
/// <para>
/// This column master key will be backed by the <see cref="SqlColumnEncryptionCspProvider"/> class.
/// </para>
/// </remarks>
/// <param name="connection">The SQL connection used to interact with the database.</param>
/// <param name="namePrefix">The column master key name.</param>
/// <param name="cspProvider">The certificate to wrap. Must contain a CSP-backed private key.</param>
/// <param name="allowEnclaveComputations"><c>true</c> to enable enclave computations.</param>
public CspProviderBackedColumnMasterKey(SqlConnection connection, string namePrefix,
CspCertificateFixture cspProvider, bool allowEnclaveComputations)
: base(connection, namePrefix, GenerateCreationParameters(cspProvider, allowEnclaveComputations))
{
}

private static CreationParameters GenerateCreationParameters(CspCertificateFixture cspProvider, bool allowEnclaveComputations) =>
new(provider: new SqlColumnEncryptionCspProvider(),
providerName: SqlColumnEncryptionCspProvider.ProviderName,
cspProvider.CspKeyPath ?? throw new InvalidOperationException("Certificate lacks a CSP key."),
allowEnclaveComputations);
}

/// <summary>
/// A column master key backed by a certificate. Created at the start of its scope and dropped when disposed.
/// </summary>
public sealed class CertificateBackedColumnMasterKey : ColumnMasterKey
{
/// <summary>
/// Initializes a new instance of the CertificateBackedColumnMasterKey class using the specified
/// SQL connection, name and a certificate.
/// </summary>
/// <remarks>
/// <para>
/// If a column master key with the specified name already exists, it will be dropped automatically
/// before creation.
/// </para>
/// <para>
/// This column master key will be backed by the <see cref="SqlColumnEncryptionCertificateStoreProvider"/>
/// class.
/// </para>
/// </remarks>
/// <param name="connection">The SQL connection used to interact with the database.</param>
/// <param name="namePrefix">The column master key name.</param>
/// <param name="cspCertificate">The certificate to wrap. Must contain a private key.</param>
/// <param name="allowEnclaveComputations"><c>true</c> to enable enclave computations.</param>
public CertificateBackedColumnMasterKey(SqlConnection connection, string namePrefix,
CspCertificateFixture cspCertificate, bool allowEnclaveComputations)
: base(connection, namePrefix, GenerateCreationParameters(cspCertificate.CspCertificatePath, allowEnclaveComputations))
{
}

/// <summary>
/// Initializes a new instance of the ColumnMasterKey class using the specified SQL connection,
/// name and a certificate.
/// </summary>
/// <remarks>
/// <para>
/// If a column master key with the specified name already exists, it will be dropped automatically
/// before creation.
/// </para>
/// <para>
/// This column master key will be backed by the <see cref="SqlColumnEncryptionCertificateStoreProvider"/>
/// class.
/// </para>
/// </remarks>
/// <param name="connection">The SQL connection used to interact with the database.</param>
/// <param name="namePrefix">The column master key name.</param>
/// <param name="cmkCertificate">The certificate to wrap. Must contain a private key.</param>
/// <param name="allowEnclaveComputations"><c>true</c> to enable enclave computations.</param>
public CertificateBackedColumnMasterKey(SqlConnection connection, string namePrefix,
ColumnMasterKeyCertificateFixture cmkCertificate, bool allowEnclaveComputations)
: base(connection, namePrefix, GenerateCreationParameters(
cmkCertificate.ColumnMasterKeyCertificatePath
?? throw new InvalidOperationException("Certificate has not been created."),
allowEnclaveComputations))
{
}

private static CreationParameters GenerateCreationParameters(string certificatePath, bool allowEnclaveComputations) =>
new(provider: new SqlColumnEncryptionCertificateStoreProvider(),
providerName: SqlColumnEncryptionCertificateStoreProvider.ProviderName,
certificatePath,
allowEnclaveComputations);
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,31 @@
<ItemGroup>
<ProjectReference Include="$(RepoRoot)src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj"
Condition="'$(ReferenceType)' != 'Package'" />
<!-- In Package mode the perf pipeline can pin a specific released MDS version (e.g. a -->
<!-- performance baseline) by passing -p:MdsPackageVersion=<version>. That property flows -->
<!-- to every project in the build, so this project must honour it too; otherwise the -->
<!-- CPM-managed (unpublished, in-development) version is used here and restore fails with -->
<!-- NU1102/NU1605 for consumers that pinned an older baseline. Under Central Package -->
<!-- Management a plain Version is ignored, so VersionOverride is used; when -->
<!-- MdsPackageVersion is empty the CPM-managed version applies. The two Includes are -->
<!-- mutually exclusive on whether MdsPackageVersion was provided. -->
<PackageReference Include="Microsoft.Data.SqlClient"
Condition="'$(ReferenceType)' == 'Package'" />
Condition="'$(ReferenceType)' == 'Package' and '$(MdsPackageVersion)' == ''" />
<PackageReference Include="Microsoft.Data.SqlClient"
Condition="'$(ReferenceType)' == 'Package' and '$(MdsPackageVersion)' != ''"
VersionOverride="$(MdsPackageVersion)" />
</ItemGroup>

<!-- Microsoft.Data.SqlClient 7.1.0-preview1.26124.5 declares transitive dependencies on the -->
<!-- prerelease Microsoft.Data.SqlClient.Extensions.Abstractions and -->
<!-- Microsoft.Data.SqlClient.Internal.Logging 1.0.0-preview1.26124.5 packages, which were never -->
<!-- published to NuGet.org. Restoring that exact baseline therefore fails with NU1603 (the -->
<!-- prerelease dependency is not found and the released 1.0.0 is substituted). Pin explicit -->
<!-- direct references to the released 1.0.0 packages - which satisfy the (>= prerelease) -->
<!-- constraint - but only for that specific baseline version so other builds are unaffected. -->
<ItemGroup Condition="'$(ReferenceType)' == 'Package' and '$(MdsPackageVersion)' == '7.1.0-preview1.26124.5'">
<PackageReference Include="Microsoft.Data.SqlClient.Extensions.Abstractions" VersionOverride="1.0.0" />
<PackageReference Include="Microsoft.Data.SqlClient.Internal.Logging" VersionOverride="1.0.0" />
</ItemGroup>

<!-- References for netfx -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ public static void FlushResultSet(this SqlDataReader dataReader)
{
while (dataReader.Read())
{
// Discard results.
// Read all row data and discard.
_ = dataReader.IsDBNull(0);
}
}

Expand All @@ -58,7 +59,8 @@ public static async Task FlushResultSetAsync(this SqlDataReader dataReader)
{
while (await dataReader.ReadAsync())
{
// Discard results.
// Read all row data and discard.
_ = await dataReader.IsDBNullAsync(0);
}
}
}
Expand Down
Loading
Loading