4

I am trying to somehow programamtically log the CPU frequency of my windows 10 machine. However I apparently fail to get the current frequency as shown in the task manager.

in Powershell, using

get-wmiobject Win32_Processor -Property CurrentClockSpeed

does only return a clock speed that is exactly the maximum one (even though i can see in task manager that it is not running that high)

I even tried this solution: https://www.remkoweijnen.nl/blog/2014/07/18/get-actual-cpu-clock-speed-powershell/ but it did not give me anything but a static value = max value.

Even python's psutil does only return a static value.

Does anybody know how to get around this and actually somehow log the CPU frequency each x seconds?

any help would be appreciated, thanks!

Tom
  • 906
  • 1
  • 8
  • 30

2 Answers2

10

TLDR: To find the Current Processor Frequency, you have to use the % Processor Performance performance counter:

$MaxClockSpeed = (Get-CimInstance CIM_Processor).MaxClockSpeed
$ProcessorPerformance = (Get-Counter -Counter "\Processor Information(_Total)\% Processor Performance").CounterSamples.CookedValue
$CurrentClockSpeed = $MaxClockSpeed*($ProcessorPerformance/100)

Write-Host "Current Processor Speed: " -ForegroundColor Yellow -NoNewLine
Write-Host $CurrentClockSpeed

The more in depth explanation as to why does querying WMI Win32_Processor for CurrentClockSpeed seem to always return the maximum frequency rather than the actual "Current Clock Speed"? In fact, why do all of the dozens of WMI/CMI/Perfmon counters all seem to return the "wrong" frequency? If CPU-Z and Task Manager can get it, what do we have to do to get the "actual" frequency? To answer that, we need to understand what CurrentClockSpeed is actually returning.

From the WMI documentation for Win32_Processor CurrentClockSpeed:

Current speed of the processor, in MHz. This value comes from the Current Speed member of the Processor Information structure in the SMBIOS information.

Great! One would think that this simple query should get us the current frequency. This worked great a dozen years ago, but nowadays it doesn't; because it really only works for two very specific cases:

  1. When you have a processor that only runs at its defined stock speed.
  2. When a mobile processor is asked by Windows to run at a different speed (e.g. moving to battery mode).

At startup, Widows gets the processor information and gets the Current Clock Speed. Most people are running their processor at the recommended settings so Current Clock Speed == Max Clock Speed, which mean that the two numbers match all the time. When you change power states, Windows will change the frequency, and CurrentClockSpeed will be changed as well.

Now, what happened a dozen years ago to essentially make CurrentClockSpeed completely inaccurate/irrelevant? You can ultimately thank Intel. They essentially blew this whole ideal value out of the water thanks to a new technology called Turbo Boost.

What does Turbo Boost have to do with this?

Turbo Boost dynamically changes the processor frequency based on the current load on the processor within the confines of voltage, current, and thermal envelopes. Almost all modern processors also now have power saving modes and can dynamically change their frequencies based on their current marketing buzzword (e.g. Turbo Boost (up), Cool'N'Quiet (down)).

The key point is: all this frequency moving up/down/off/on is all automatically done without Windows knowing about it. Because Windows doesn't know about it, the CurrentClockSpeed value could be completely inaccurate most of the time. In fact, Microsoft knows this, and when you open your Performance Monitor, and you look at the description under Processor Performance/Processor Frequency:

Processor Frequency is the frequency of the current processor in megahertz. Some processors are capable of regulating their frequency outside of the control of Windows. Processor Frequency will not accurately reflect actual processor frequency on these systems. Use Processor Information\% Processor Performance instead.

Fortunately this description gives us a hint of what we have to use to get the actual value: Processor Information\% Processor Performance

We can use Get-Counter to access the current Processor performance like so:

PS C:\> Get-Counter -Counter "\Processor Information(_Total)\% Processor Performance"

Timestamp                 CounterSamples
---------                 --------------
2020-01-01 1:23:45 AM     \\HAL9256\processor information(_total)\% processor performance :
                          153.697654229441

Here, you can see that my processor is running at 153% performance a.k.a. 153% of the frequency of the processor (yay for Turbo Boost!). We then query the MaxClockSpeed from CIM_Processor class (you can use WMI_Processor as well):

PS C:\> (Get-CimInstance CIM_Processor).MaxClockSpeed
2592

In order to calculate out the actual clock speed:

$MaxClockSpeed = (Get-CimInstance CIM_Processor).MaxClockSpeed
$ProcessorPerformance = (Get-Counter -Counter "\Processor Information(_Total)\% Processor Performance").CounterSamples.CookedValue
$CurrentClockSpeed = $MaxClockSpeed*($ProcessorPerformance/100)

Write-Host "Current Processor Speed: " -ForegroundColor Yellow -NoNewLine
Write-Host $CurrentClockSpeed

Then wrapping it up in a loop if you need it to run every 2 seconds (Ctrl+C to stop):

$MaxClockSpeed = (Get-CimInstance CIM_Processor).MaxClockSpeed

While($true){
    $ProcessorPerformance = (Get-Counter -Counter "\Processor Information(_Total)\% Processor Performance").CounterSamples.CookedValue
    $CurrentClockSpeed = $MaxClockSpeed*($ProcessorPerformance/100)

    Write-Host "Current Processor Speed: " -ForegroundColor Yellow -NoNewLine
    Write-Host $CurrentClockSpeed

    Sleep -Seconds 2
}
HAL9256
  • 12,384
  • 1
  • 34
  • 46
  • While the code works for receiving a higher value than the base clock, it doesn't seem to match with e.g. HWInfo64 on my Zen 3 Ryzen 5900X. It's always a couple of MHz lower in direct comparison. Also, the (_Total) entry reports even lower for me, I need to directly query the various cores to get a more precise measurement. For example: `Processor Information(_Total): 4725,68` vs `Processor Information(0,2): 4837,54` – sp00n Mar 10 '21 at 00:35
  • @sp00n you point out an interesting case, which is directly created by the Zen 3 Ryzen. The calculation above makes the assumption that, while clock speed can change dynamically, the changes happen to **all** the cores at the same time. With the Zen 3 Ryzen, each core can run at *different* clock speeds and/or sleep when idle. This will throw off these basic calculations by quite a bit (esp. If one core is sleeping). In your case, yes, you will have to query each individual core, and power state, to "properly" calculate the clock speed. – HAL9256 Mar 24 '21 at 00:06
  • The values reported for the individual cores are still lower than what HWInfo64 or Ryzen Master report, and I don't know why. In the end I even did the calculation myself with the raw performance counter values instead of the already "cooked" ones (you need two snapshots of those to calculate the value), but this only gave me very slightly more accurate numbers. It's still missing a couple of MHz to the value of the dedicated tools, and apparently the higher the boost clock becomes, the greater the difference will be as well (e.g. 5000 MHz boost ~48xx MHz in the performance counter). – sp00n Mar 25 '21 at 11:01
0

With help of the PS code above and the doc of win32pdh, I'm able to get it work in Python:

from win32pdh import PDH_FMT_DOUBLE
from win32pdh import OpenQuery, CloseQuery, AddCounter
from win32pdh import CollectQueryData, GetFormattedCounterValue

def get_freq():
    ncores = 16

    paths = []
    counter_handles = []
    query_handle = OpenQuery()
    for i in range(ncores):
        paths.append("\Processor Information(0,{:d})\% Processor Performance".format(i))
        counter_handles.append(AddCounter(query_handle, paths[i]))
    
    CollectQueryData(query_handle)
    time.sleep(1)
    CollectQueryData(query_handle)

    freq = []
    for i in range(ncores):
        (counter_type, value) = GetFormattedCounterValue(counter_handles[i], PDH_FMT_DOUBLE)
        freq.append(value*2.496/100) 
        # 2.496 is my base speed, I didn't spend time to automate this part

    # print("{:.3f} Ghz".format(max(freq)))
    CloseQuery(query_handle)
    return "{:.3f} GHz".format(max(freq))
PhoenixQ
  • 123
  • 1
  • 2
  • 5