Skip to content

Conversation

@ramartinez7
Copy link

Fixes #1520

This PR adds the ability to specify custom hardware counter names by their ETW profile source names, giving developers flexibility to use CPU-specific counters that aren't covered by the HardwareCounter enum.

Changes:

  • Add CustomCounter class to represent custom PMC counters with configurable profile source name, short display name, interval, and higher-is-better flag
  • Add CustomCountersAttribute for declarative counter specification
  • Add IConfig.GetCustomCounters() and ManualConfig.AddCustomCounters() methods
  • Update ImmutableConfig/ImmutableConfigBuilder to handle custom counters
  • Update PreciseMachineCounter to support both enum and custom counters
  • Update PmcStats to track custom counters separately
  • Update PmcMetricDescriptor to use ShortName for column display
  • Update EtwProfiler to collect both hardware and custom counters
  • Update HardwareCounters.Validate() and Sessions to handle custom counters
  • Fix ManualConfig.Add() to propagate custom counters during config merge

Usage example:
// Using attribute
[CustomCounters("DCMiss", "ICMiss", "BranchMispredictions")] public class MyBenchmark { }

// Using ManualConfig
var config = ManualConfig.CreateEmpty()
.AddCustomCounters(
new CustomCounter("DCMiss", shortName: "L1D$Miss"),
new CustomCounter("ICMiss", shortName: "L1I$Miss")
);

Developers can discover available counters on their system using: TraceEventProfileSources.GetInfo().Keys

Note: This is an initial implementation that has not been
thoroughly tested across different CPU architectures or edge cases. Further review
and testing is recommended before merging to confirm correctness of the changes.

If you spot issues or have improvements in mind,
please feel free to push commits directly to this branch or open your own PR with the changes.

Fixes dotnet#1520

This PR adds the ability to specify custom hardware counter names by their
ETW profile source names, giving developers flexibility to use CPU-specific
counters that aren't covered by the HardwareCounter enum.

Changes:
- Add CustomCounter class to represent custom PMC counters with configurable
  profile source name, short display name, interval, and higher-is-better flag
- Add CustomCountersAttribute for declarative counter specification
- Add IConfig.GetCustomCounters() and ManualConfig.AddCustomCounters() methods
- Update ImmutableConfig/ImmutableConfigBuilder to handle custom counters
- Update PreciseMachineCounter to support both enum and custom counters
- Update PmcStats to track custom counters separately
- Update PmcMetricDescriptor to use ShortName for column display
- Update EtwProfiler to collect both hardware and custom counters
- Update HardwareCounters.Validate() and Sessions to handle custom counters
- Fix ManualConfig.Add() to propagate custom counters during config merge

Usage example:
// Using attribute
[CustomCounters("DCMiss", "ICMiss", "BranchMispredictions")]
public class MyBenchmark { }

// Using ManualConfig
var config = ManualConfig.CreateEmpty()
    .AddCustomCounters(
        new CustomCounter("DCMiss", shortName: "L1D$Miss"),
        new CustomCounter("ICMiss", shortName: "L1I$Miss")
    );

Developers can discover available counters on their system using:
TraceEventProfileSources.GetInfo().Keys
Copilot AI review requested due to automatic review settings January 4, 2026 00:03
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds support for custom hardware performance counter names, allowing developers to specify CPU-specific counters by their ETW profile source names that aren't covered by the built-in HardwareCounter enum. This addresses a real need for AMD-specific counters and other architecture-specific performance monitoring capabilities.

Key Changes:

  • Introduced CustomCounter class and CustomCountersAttribute for declarative counter specification
  • Extended config infrastructure (IConfig, ManualConfig, ImmutableConfig) to support custom counters alongside hardware counters
  • Updated ETW profiling infrastructure to collect and validate both built-in and custom performance counters

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/BenchmarkDotNet/Diagnosers/CustomCounter.cs New class representing custom PMC counters with configurable profile source name, display name, interval, and directionality
src/BenchmarkDotNet/Attributes/CustomCountersAttribute.cs New attribute for declaratively specifying custom counters on benchmark classes or assemblies
src/BenchmarkDotNet/Configs/IConfig.cs Added GetCustomCounters() method to config interface
src/BenchmarkDotNet/Configs/ManualConfig.cs Added customCounters collection and AddCustomCounters() method with config merging support
src/BenchmarkDotNet/Configs/ImmutableConfig.cs Added custom counters storage and retrieval in immutable config
src/BenchmarkDotNet/Configs/ImmutableConfigBuilder.cs Updated to handle custom counters during config building and diagnoser loading
src/BenchmarkDotNet/Configs/DefaultConfig.cs Returns empty collection for custom counters in default config
src/BenchmarkDotNet/Configs/DebugConfig.cs Returns empty collection for custom counters in debug config
src/BenchmarkDotNet/Diagnosers/PreciseMachineCounter.cs Added custom counter support with new constructor and properties for short name and directionality
src/BenchmarkDotNet/Diagnosers/PmcStats.cs Extended to track custom counters separately and merge them with hardware counters by profile source ID
src/BenchmarkDotNet/Diagnosers/PmcMetricDescriptor.cs Updated to use ShortName and HigherIsBetter properties from PreciseMachineCounter
src/BenchmarkDotNet.Diagnostics.Windows/EtwProfiler.cs Updated to collect both hardware and custom counters, building separate lists and concatenating them
src/BenchmarkDotNet.Diagnostics.Windows/HardwareCounters.cs Added custom counter validation and FromCustomCounter factory method with interval handling
src/BenchmarkDotNet.Diagnostics.Windows/Sessions.cs Updated to enable PMCProfile keywords when custom counters are present
tests/BenchmarkDotNet.Tests/Configs/CustomCounterTests.cs Comprehensive test suite covering CustomCounter class, config operations, merging, and integration with hardware counters

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Consolidated null and whitespace validation into single IsNullOrWhiteSpace check
as suggested in PR review. This reduces code redundancy while maintaining the
same validation behavior.
As suggested in PR review, simplified the logic to always use customCounter.Interval
directly. The CustomCounter constructor already sets DefaultInterval as the default
value, so the conditional check was redundant and could incorrectly override
explicitly-set intervals that happened to match DefaultInterval.
As suggested in PR review, added validation to detect and report collisions
between hardware counters and custom counters that share the same ProfileSourceId.
Previously, custom counters were silently dropped in case of collision, which
could confuse users. Now an InvalidOperationException is thrown with details
about the colliding counters.
Updated the comment in ImmutableConfigBuilder to reflect that the code now
handles both [HardwareCounters] and [CustomCounters] attributes when dynamically
loading the diagnoser.
Improved the error message to only show ellipsis when there are actually more
than 20 available counters. Now displays '(and X more)' to indicate exactly
how many counters were omitted from the list, providing clearer feedback to users.
Added ArgumentOutOfRangeException validation to reject zero or negative
interval values in CustomCounter constructor. Non-positive intervals don't
make sense for PMC sampling and could cause runtime errors in the ETW API.
Updated tests to verify that zero and negative intervals are properly rejected.
Changed the foreach loop to use explicit .Where() filtering instead of
implicit if-based filtering, as suggested in code review. This makes the
filtering logic more explicit and aligns with functional programming style.
@ramartinez7
Copy link
Author

@ramartinez7 please read the following Contributor License Agreement(CLA). If you agree with the CLA, please reply with the following information.

@dotnet-policy-service agree [company="{your company}"]

Options:

  • (default - no company specified) I have sole ownership of intellectual property rights to my Submissions and I am not making Submissions in the course of work for my employer.
@dotnet-policy-service agree
  • (when company given) I am making Submissions in the course of work for my employer (or my employer has intellectual property rights in my Submissions by contract or applicable law). I have permission from my employer to make Submissions and enter into this Agreement on behalf of my employer. By signing below, the defined term “You” includes me and my employer.
@dotnet-policy-service agree company="Microsoft"

Contributor License Agreement

@dotnet-policy-service agree

@timcassell
Copy link
Collaborator

Please see prior art in #1438.

CustomCounter name is unintuitive. I think we should keep HardwareCounter, but take a breaking change and change it to a class instead of enum, with const strings for the existing options (similar to Column class).

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.

Unable to read some hardware counters on ZEN2 CPU

2 participants