Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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 @@ -127,7 +127,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 @@ -267,7 +267,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);
}
Loading
Loading