BenchmarkDotNet icon indicating copy to clipboard operation
BenchmarkDotNet copied to clipboard

Fix "Unknown Processor" on Windows if WMIC is not present

Open alastairlundy opened this issue 5 months ago • 15 comments

This PR fixes #2747 by adding the WmiLightCpuDetector detector class that detects CPU info using the WmiLight library.

This PR also updates WmicCpuDetector to also check for the presence of WMIC.exe at the expected file path to check for applicability so that it isn't used on systems without WMIC.exe .

The output is as expected when tested. For testing I used the BenchmarkDotNet Samples project.

// * Summary *

BenchmarkDotNet v0.15.1-develop (2025-06-04), Windows 11 (10.0.26100.4061/24H2/2024Update/HudsonValley)
12th Gen Intel Core i7-1270P 2.20GHz, 1 CPU, 16 logical and 12 physical cores
.NET SDK 9.0.300
  [Host]     : .NET 8.0.16 (8.0.1625.21506), X64 RyuJIT AVX2 [AttachedDebugger]
  DefaultJob : .NET 8.0.16 (8.0.1625.21506), X64 RyuJIT AVX2

alastairlundy avatar Jun 04 '25 21:06 alastairlundy

@dotnet-policy-service agree

alastairlundy avatar Jun 04 '25 21:06 alastairlundy

Implementation is incomplete. Mark it ready for review when it's ready.

timcassell avatar Jun 07 '25 21:06 timcassell

@timcassell It's ready for review but I've noticed in the WmicCpuDetector parser something. Should the Max CPU Frequency be added to, with += rather than assignment, if multiple processors are detected? I'm inclined to say no but wanted to check.


            if (processor.TryGetValue(WmicCpuInfoKeyNames.MaxClockSpeed, out string frequencyValue)
                && int.TryParse(frequencyValue, out int frequency)
                && frequency > 0)
            {
                sumMaxFrequency += frequency;
            }

alastairlundy avatar Jun 07 '25 22:06 alastairlundy

It looks like that is used to calculate an "average" across CPUs sumMaxFrequency / processorsCount. I don't understand what the purpose of that actually is, as it seems like a meaningless value to me. But @AndreyAkinshin probably has a better grasp of it.

timcassell avatar Jun 07 '25 23:06 timcassell

Estimating the CPU's maximum frequency is not always straightforward. One of the main challenges is that some modern CPUs consist of heterogeneous cores — that is, cores with different nominal and maximum frequencies. To represent this accurately, we would technically need to track a full list of cores with individual frequency metrics. However, this seems unnecessarily complex for our use case.

First, such a detailed model would usually result in a verbose and redundant dataset — in most real-world scenarios, many cores share identical characteristics. Second, we’d still need to reduce this data to a single representative number for downstream analyses (e.g., in ZeroMeasurementAnalyzer), which defeats the purpose of maintaining fine-grained data.

After some consideration, I propose the following pragmatic approach:

  • MaxFrequency should be defined as the maximum of all reported per-core frequencies. This may not accurately reflect the maximum frequency of all cores (especially low-power ones in heterogeneous setups), but it aligns with our goal of capturing the upper bound of the CPU’s capabilities.

  • NominalFrequency should be defined as the minimum of all reported frequencies (excluding non-positive or clearly incorrect values). This gives us a baseline that roughly reflects the “typical” operating frequency. When different from MaxFrequency, it provides a useful range to understand performance scaling behavior.

What do you think?

AndreyAkinshin avatar Jun 08 '25 11:06 AndreyAkinshin

After some consideration, I propose the following pragmatic approach:

* **MaxFrequency** should be defined as the _maximum_ of all reported per-core frequencies. This may not accurately reflect the maximum frequency of all cores (especially low-power ones in heterogeneous setups), but it aligns with our goal of capturing the upper bound of the CPU’s capabilities.

* **NominalFrequency** should be defined as the _minimum_ of all reported frequencies (excluding non-positive or clearly incorrect values). This gives us a baseline that roughly reflects the “typical” operating frequency. When different from `MaxFrequency`, it provides a useful range to understand performance scaling behavior.

What do you think?

Makes sense. Should this be worked on in this PR or a new one? And how do we want to format the resulting clockspeeds? Something like: CPU Name, Number of Processors, Logical Processors, Nominal Frequency GHz, Maximum Frequency GHz ?

alastairlundy avatar Jun 08 '25 16:06 alastairlundy

Should this be worked on in this PR or a new one?

Since we are introducing a new detector, it makes sense to implement a solution that addresses this concern. Let's do it in this PR.

And how do we want to format the resulting clockspeeds?

The detector should not be concerned about the formatting. Its purpose is solely to detect the CPU properties and return a data model.

AndreyAkinshin avatar Jun 08 '25 16:06 AndreyAkinshin

The detector should not be concerned about the formatting. Its purpose is solely to detect the CPU properties and return a data model.

Understood.

alastairlundy avatar Jun 08 '25 17:06 alastairlundy

LGTM

@alastairlundy could you please resolve the conflict with the master branch @timcassell what do you think?

AndreyAkinshin avatar Jun 09 '25 12:06 AndreyAkinshin

Since the max/nominal frequency calculations are changed here, all the other cpu detectors need to be updated as well.

timcassell avatar Jun 09 '25 18:06 timcassell

Since the max/nominal frequency calculations are changed here, all the other cpu detectors need to be updated as well.

The macOS detector & parser are able to get the nominal frequency already without any averaging so I think that's fine to leave as is. I copied the Min/Max of nominal and max frequency from PowershellWmiCpuDetector to MosCpuDetector just now, and added it before to WmicCpuDetector. So all of the Windows supported Cpu detectors now behave the same (for detecting nominal and max frequency).

The LinuxCpuDetector however seems to follow a completely different methodology for getting nominal clockspeed.

@timcassell Do you want me to update the LinuxCpuDetector to also get nominal clockspeed the same way?

alastairlundy avatar Jun 09 '25 19:06 alastairlundy

Yeah the Linux one seems strange, but I think it should also be updated to match behavior.

timcassell avatar Jun 09 '25 19:06 timcassell

Maybe MacOS detector could use a null check, but other than that LGTM.

timcassell avatar Jun 09 '25 20:06 timcassell

@alastairlundy Please fix failing tests.

timcassell avatar Jun 10 '25 21:06 timcassell

@alastairlundy Please fix failing tests.

The CPU detection and parsing tests pass now.

alastairlundy avatar Jun 11 '25 13:06 alastairlundy

LGTM. @alastairlundy, please resolve the conflicts with the latest master, and we will be ready for the merge.

AndreyAkinshin avatar Jun 16 '25 16:06 AndreyAkinshin

LGTM. @alastairlundy, please resolve the conflicts with the latest master, and we will be ready for the merge.

It's resolved

alastairlundy avatar Jun 16 '25 17:06 alastairlundy

@alastairlundy GitHub indicates that there are still conflicts preventing an automatic merge. The easiest way to resolve this issue is to squash your commits, rebase onto the latest master, and force-push your branch.

AndreyAkinshin avatar Jun 16 '25 20:06 AndreyAkinshin

No problem with squash and merge

timcassell avatar Jun 16 '25 20:06 timcassell