Skip to content

Refactor benchmarks#1203

Open
fdcastel wants to merge 36 commits intoFirebirdSQL:masterfrom
fdcastel:refactor-benchmarks
Open

Refactor benchmarks#1203
fdcastel wants to merge 36 commits intoFirebirdSQL:masterfrom
fdcastel:refactor-benchmarks

Conversation

@fdcastel
Copy link
Member

Based on #1200 for now. I will rebase it with latest master in the future.

  • Rename project Perf to FirebirdSql.Data.FirebirdClient.Benchmarks.
  • Update project to use .NET8.
  • Upgrade BenchmarkDotNet to version 0.14.0.
  • Update baseline nuget package to v10.3.1.
  • Add /BenchmarkDotNet.Artifacts folder to .gitignore.
  • Pass command-line arguments to BenchmarkDotNet engine.
  • Apply SQL Formatting. Use raw strings.
  • Add script run-benchmark.ps1.

@fdcastel fdcastel force-pushed the refactor-benchmarks branch from bd2e3a0 to e5e4075 Compare May 18, 2025 01:19
@fdcastel
Copy link
Member Author

Rebase with master.

fdcastel added 25 commits March 22, 2026 21:43
StartActivity() returns null when no listeners are interested in the
activity. The code accessed activity.IsAllDataRequested without a null
check, causing a NullReferenceException. Use the Npgsql-style pattern
'activity is not { IsAllDataRequested: true }' for early return.
Stopwatch ticks are not TimeSpan ticks when Stopwatch.IsHighResolution
is true (most systems). TimeSpan.FromTicks(stopwatchTicks) produces
incorrect durations. Use Stopwatch.GetElapsedTime() which correctly
handles the frequency conversion.
The semantic conventions renamed db.system to db.system.name. The
well-known value for Firebird is 'firebirdsql', not 'firebird'.
Updated in both FbActivitySource (spans) and FbConnection (metrics).
The semantic conventions mark db.namespace as Conditionally Required
when available. The connection's Database property is used as the value.
The semantic conventions mark error.type as Conditionally Required on
failure. For FbException, use the SQLSTATE code; for other exceptions,
use the full exception type name.
Per OTel semantic conventions, db.query.text and db.query.parameter.*
are Opt-In level attributes that may expose sensitive data. They should
not be collected by default. Added IsQueryTextTracingEnabled flag and
reuse IsParameterLoggingEnabled for parameter tracing.
Replace hardcoded '1.0.0' version with the assembly's informational
version, keeping the telemetry version in sync with the NuGet package.
…ecording

TraceCommandException now records metrics immediately and resets
_startedAtTicks so the later TraceCommandStop (called during Release)
does not record a second, inflated duration that includes cleanup time.
- Remove db.transaction_id custom tag (not in OTel spec, causes server
  round-trip per traced command)
- Remove commented-out db.snapshot_id code
- Remove deprecated exception.escaped attribute
- Remove NormalizeDbNull helper, inline DBNull check
- Set server.port on spans when using a non-default port
- Set db.stored_procedure.name for StoredProcedure command types
- Fix server.address in MetricsConnectionAttributes to not concatenate
  the port (server.port is now a separate attribute)
- Clean up leftover placeholder comments
Set db.query.summary as the low-cardinality activity name on all spans.
For Text commands, extract the first SQL verb (SELECT, INSERT, etc.)
and use it as db.operation.name and in the activity name.
Debug.Assert checks that TraceCommandStart was called before the stop
or exception path, helping catch lifecycle bugs during development.
Prevents potential NullReferenceException if metrics are recorded
before the connection string is set.
Change GetMetrics() to return IEnumerable of tuples via Select instead
of allocating a Dictionary via ToDictionary on every collection cycle.
Also simplify GetConnectionMax to use Select instead of SelectMany.
Suggest default histogram bucket boundaries for operation duration and
connection create time histograms, improving out-of-the-box resolution.
Guarded by NET9_0_OR_GREATER since InstrumentAdvice is a .NET 9 API.
Expose the telemetry source names as public constants so consumers can
easily subscribe: builder.AddSource(FbTelemetry.ActivitySourceName) and
builder.AddMeter(FbTelemetry.MeterName). Internal code updated to use
the constants.
Document the ActivitySource and Meter names, available span attributes,
opt-in sensitive attributes, and available metrics with their types and
semantic convention references.
- Rename project 'Perf' to 'FirebirdSql.Data.FirebirdClient.Benchmarks'.
- Update project to use .net8.
- Upgrade BenchmarkDotNet to version 0.14.0.
- Update baseline nuget package to v10.3.1.
- Add /BenchmarkDotNet.Artifacts to .gitignore.
- Pass command-line arguments to BenchmarkDotNet engine.
- Apply SQL Formatting. Use raw strings.
- Add script run-benchmark.ps1.
@fdcastel fdcastel force-pushed the refactor-benchmarks branch 2 times, most recently from 2a8789b to 1ec795d Compare March 23, 2026 03:31
@fdcastel
Copy link
Member Author

Following up on the initial refactoring, these additional changes were made on the branch:

Infrastructure

  • Extracted a shared BenchmarkConfig : ManualConfig class to eliminate the duplicated inner Config class that existed independently in each benchmark class. Job configuration is now defined in a single place.
  • Extracted a BenchmarkBase abstract class holding the ConnectionString, CreateDatabase(), and GlobalCleanup() members that were duplicated across benchmark classes.
  • The connection string can now be overridden via the FIREBIRD_BENCHMARK_CS environment variable (falls back to the existing localhost default), enabling use in environments with a non-default Firebird setup.

Configuration

  • Updated the benchmark toolchain from .NET 8 to .NET 10, matching the project's TargetFramework.
  • Removed WithPlatform(Platform.X64) and WithJit(Jit.RyuJit) — both are no-ops on .NET 8+ (x64 + RyuJIT is the only supported configuration) and were just noise.
  • Added MarkdownExporter.GitHub so each run automatically writes a GitHub-flavored Markdown table to BenchmarkDotNet.Artifacts/, ready to paste into issues or PRs.
  • Added DefaultOrderer(SummaryOrderPolicy.FastestToSlowest) to make the results table easier to scan.

Benchmark methods

  • Single-value [Params(100)] and [Params(100_000)] replaced with plain constants — a single-value [Params] adds a column to the results table without varying anything.
  • Fetch methods now return the last read value instead of discarding it, to make dead-code elimination by the JIT impossible.
  • Added async variants (ExecuteAsync, FetchAsync, OpenCloseAsync) for all sync benchmarks. Async ADO.NET paths have different allocation profiles (state machines, Task wrappers) and are the primary path in web workloads.

New benchmarks

  • ConnectionBenchmark — measures connection open/close throughput (sync and async), which exercises connection pool logic.
  • LargeFetchBenchmark — added to the committed tree (was previously an untracked file).

Script

  • run-benchmark.ps1 now accepts -Benchmark ConnectionBenchmark in addition to the existing values, and two new optional switches: -Disasm (enables --disasm) and -Profile (enables ETW profiling on Windows).

Documentation

  • Added docs/benchmark.md describing prerequisites, how to run, connection string configuration, available benchmarks, and where results are written.

@fdcastel
Copy link
Member Author

Future improvements:

  1. async benchmarks
  2. connection-pool benchmarks
  3. configurable connection string
  4. Remove Duplicated config (use a shared base)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants