BlazorAmpowering.Coverage.Runtime
1.0.0
dotnet add package BlazorAmpowering.Coverage.Runtime --version 1.0.0
NuGet\Install-Package BlazorAmpowering.Coverage.Runtime -Version 1.0.0
<PackageReference Include="BlazorAmpowering.Coverage.Runtime" Version="1.0.0" />
<PackageVersion Include="BlazorAmpowering.Coverage.Runtime" Version="1.0.0" />
<PackageReference Include="BlazorAmpowering.Coverage.Runtime" />
paket add BlazorAmpowering.Coverage.Runtime --version 1.0.0
#r "nuget: BlazorAmpowering.Coverage.Runtime, 1.0.0"
#:package BlazorAmpowering.Coverage.Runtime@1.0.0
#addin nuget:?package=BlazorAmpowering.Coverage.Runtime&version=1.0.0
#tool nuget:?package=BlazorAmpowering.Coverage.Runtime&version=1.0.0
BlazorAmpowering.Coverage.Runtime
Runtime registry and OpenTelemetry/Prometheus exporter for production code coverage in .NET and Blazor applications.
This package is the runtime counterpart of BlazorAmpowering.Coverage (the Fody IL weaver). The weaver injects call-tracking instructions into your assemblies at build time; this package provides the shared registry those instructions write into, and the OTel instrumentation that exports the data to Prometheus / Mimir.
Relationship between the two packages
Build time
──────────
BlazorAmpowering.Coverage (weaver)
├── Injects Hit(slot) calls into every method body
└── Generates a module initializer that calls CoverageRegistry.Register()
Runtime
───────
BlazorAmpowering.Coverage.Runtime
├── CoverageRegistry ← receives Register() and Hit() calls from instrumented assemblies
└── OTel instruments ← ObservableGauge + ObservableCounter → Prometheus / Mimir
The weaver package is a build-time-only tool (PrivateAssets="all" in the consuming project). Your libraries do not carry a dependency on OpenTelemetry — only the host entry project does, through this runtime package.
Packages
| Package | Role |
|---|---|
BlazorAmpowering.Coverage |
Fody weaver — add to every project you want to instrument |
BlazorAmpowering.Coverage.Runtime |
Runtime registry + OTel/Prometheus export — entry project only |
Installation
Add this package to the project that starts your application (Blazor Server, ASP.NET Core Web API, etc.):
<ItemGroup>
<PackageReference Include="BlazorAmpowering.Coverage.Runtime" Version="1.0.0" />
</ItemGroup>
Register the OTel pipeline in Program.cs:
builder.AddProductionCoverage();
Place this after builder.AddBlazorTelemetry() when using BlazorAmpowering.Observability, so both pipelines share the same OTel MeterProvider and push to the same endpoint.
This package is needed once, in the entry project. Every library project only needs the weaver (
BlazorAmpowering.Coverage). Libraries do not reference this package directly.
How CoverageRegistry works internally
Registration phase (Register)
When the CLR loads an instrumented assembly for the first time it executes the module initializer generated by the weaver. That initializer calls:
int baseSlot = CoverageRegistry.Register(methodCount, methodMeta);
Register() is thread-safe and additive:
- Acquires an internal
lockaround the shared arrays. - Calls
Array.Resizeto extend every array bymethodCountslots. - Copies the metadata strings (
class,method,file,line) into the newly allocated slots. - Returns the base offset — the index of the first slot allocated for this assembly.
The base offset is stored in a static int s_baseSlot field generated by the weaver inside the assembly. Instrumented methods compute their absolute slot as s_baseSlot + localSlot at call time.
Multiple assemblies can register concurrently (e.g., during parallel static initialization). Because Register() serializes all resizes through the lock, there are no torn reads or overlapping slot ranges.
Hit recording phase (Hit)
Every instrumented method calls:
CoverageRegistry.Hit(baseSlot + localSlot);
Hit() is designed to be as fast as possible:
- Snapshots
_hitMapand_callMaparray references locally (one read each, no lock). - Performs an unsigned bounds check (
(uint)slotIndex >= (uint)hitMap.Length) — a single branch, predicted as not-taken after the first call. - If the method has not been hit yet, calls
Volatile.Write(ref hitMap[slotIndex], 1)— a store-release that makes the hit visible to the OTel export thread without a full memory barrier. - Always calls
Interlocked.Increment(ref callMap[slotIndex])for the call counter.
Because _hitMap and _callMap are snapshotted before access, a concurrent Register() call that replaces the array reference with a larger one is safe — Hit() will use the old (still valid) snapshot for that invocation and pick up the new array on the next call.
Per-call overhead: ~4–5 ns on a modern x64 CPU.
Prometheus metrics exported
coverage_method_info — Gauge, always 1
coverage_method_info{method_id="a1b2c3d4", class="MyApp.Services.OrderService",
method="PlaceOrder", file="OrderService.cs", line="42"} 1
This is a Prometheus info metric — a gauge permanently equal to 1 whose value is irrelevant and whose labels carry structured metadata. The same pattern is used by kube_pod_info, go_build_info, and many exporters. It allows PromQL joins (* on(method_id) group_left(class,method,file,line)) to attach rich metadata to the hit and counter metrics without repeating those strings on every scrape.
Exported once per registered method. The labels are:
| Label | Content |
|---|---|
method_id |
FNV-32 hash of the full CLR method signature — stable across compilations |
class |
Fully qualified class name |
method |
Method name as it appears in IL (includes compiler-generated suffixes for async state machines) |
file |
Source file name extracted from the PDB sequence points at weave time |
line |
First source line of the method body |
coverage_method_hit — Gauge, 0 or 1
coverage_method_hit{method_id="a1b2c3d4"} 1
1 if Hit() has been called for this method at least once since the process started, 0 otherwise. Resets to 0 on restart (no persistence). Use this metric to identify dead code: methods that are never 1 in production across a meaningful observation window are strong candidates for removal.
coverage_method_calls_total — Counter
coverage_method_calls_total{method_id="a1b2c3d4"} 4271
Monotonically increasing call count since startup. Use this metric to identify hot paths, measure load distribution across methods, and detect sudden changes in calling frequency.
Stable method_id
The method_id is computed by the weaver using the FNV-32a (Fowler–Noll–Vo) hash algorithm applied to the full CLR method name:
MyApp.Services.OrderService::PlaceOrder(System.Int32,System.Decimal)
Properties:
- Deterministic across compilations — as long as the method name and parameter types are unchanged, the ID is the same in every build. This means Prometheus time series survive a redeployment without creating new label combinations.
- Collision-resistant at scale — FNV-32 produces a 32-bit value; collisions are theoretically possible in very large assemblies (> ~65 000 methods), but in practice the probability is negligible for typical application sizes.
- Printable as hex — the ID appears in Prometheus as an 8-character lowercase hex string (e.g.,
a1b2c3d4).
PromQL queries
Overall coverage rate:
count(coverage_method_hit == 1) / count(coverage_method_hit) * 100
Methods with full metadata (for the Grafana panel — hit view):
coverage_method_hit * on(method_id) group_left(class, method, file, line)
coverage_method_info
Call counts with full metadata (for the Grafana panel — calls view):
coverage_method_calls_total * on(method_id) group_left(class, method, file, line)
coverage_method_info
Top 20 most called methods (hot paths):
topk(20, coverage_method_calls_total * on(method_id) group_left(class, method, file, line)
coverage_method_info)
Methods never called (dead code candidates):
coverage_method_hit == 0
Coverage rate per source file:
sum by(file) (coverage_method_hit * on(method_id) group_left(file) coverage_method_info)
/
sum by(file) (coverage_method_info)
* 100
Grafana panel
Install the BlazorAmpowering Grafana panel plugin and configure two Prometheus queries on the panel:
| Ref ID | Query |
|---|---|
A |
coverage_method_hit * on(method_id) group_left(class,method,file,line) coverage_method_info |
B |
coverage_method_calls_total * on(method_id) group_left(class,method,file,line) coverage_method_info |
In the panel options set Ref ID — Hit to A and Ref ID — Total calls to B. The panel renders a collapsible file → class → method tree with hit/miss badges, call counts, and coverage percentages at every level.
OTel pipeline
AddProductionCoverage() registers three OTel instruments under the meter name BlazorAmpowering.Coverage:
| Instrument | OTel type | Maps to |
|---|---|---|
coverage_method_info |
ObservableGauge<int> |
Prometheus Gauge |
coverage_method_hit |
ObservableGauge<int> |
Prometheus Gauge |
coverage_method_calls_total |
ObservableCounter<long> |
Prometheus Counter |
The instruments are compatible with any OTel metrics exporter: Prometheus pull (UseOpenTelemetryPrometheusScrapingEndpoint), OTLP push to Grafana Mimir, Azure Monitor, etc. No additional configuration is required beyond what is already set up for BlazorAmpowering.Observability.
License
Apache-2.0
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- OpenTelemetry.Extensions.Hosting (>= 1.15.3)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 82 | 5/17/2026 |