407 lines
15 KiB
C#
407 lines
15 KiB
C#
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
|
|
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
// Copyright (C) LibreHardwareMonitor and Contributors.
|
|
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
|
|
// All Rights Reserved.
|
|
|
|
using System;
|
|
using System.Diagnostics;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace LibreHardwareMonitor.Hardware.Cpu;
|
|
|
|
public class GenericCpu : Hardware
|
|
{
|
|
private const string DeferTscEstimationEnvironmentVariable = "LHM_CPU_DEFER_TSC_ESTIMATION";
|
|
private const string DeferTscEstimationSetting = "cpu.deferTscEstimation";
|
|
|
|
protected readonly int _coreCount;
|
|
protected readonly CpuId[][] _cpuId;
|
|
protected readonly uint _family;
|
|
protected readonly uint _model;
|
|
protected readonly uint _packageType;
|
|
protected readonly uint _stepping;
|
|
protected readonly int _threadCount;
|
|
|
|
private readonly CpuLoad _cpuLoad;
|
|
private double _estimatedTimeStampCounterFrequency;
|
|
private double _estimatedTimeStampCounterFrequencyError;
|
|
private readonly bool _isInvariantTimeStampCounter;
|
|
private readonly Sensor[] _threadLoads;
|
|
|
|
private readonly Sensor _totalLoad;
|
|
private readonly Sensor _maxLoad;
|
|
private readonly Vendor _vendor;
|
|
private readonly object _timeStampCounterFrequencyLock = new();
|
|
private long _lastTime;
|
|
private ulong _lastTimeStampCount;
|
|
private double _timeStampCounterFrequency;
|
|
private Task _timeStampCounterFrequencyTask;
|
|
|
|
public GenericCpu(int processorIndex, CpuId[][] cpuId, ISettings settings)
|
|
: this(processorIndex, cpuId, settings, null)
|
|
{ }
|
|
|
|
internal GenericCpu(int processorIndex, CpuId[][] cpuId, ISettings settings, HardwareStartupTrace startupTrace) : base(cpuId[0][0].Name, CreateIdentifier(cpuId[0][0].Vendor, processorIndex), settings)
|
|
{
|
|
_cpuId = cpuId;
|
|
_vendor = cpuId[0][0].Vendor;
|
|
_family = cpuId[0][0].Family;
|
|
_model = cpuId[0][0].Model;
|
|
_stepping = cpuId[0][0].Stepping;
|
|
_packageType = cpuId[0][0].PkgType;
|
|
|
|
Index = processorIndex;
|
|
_coreCount = cpuId.Length;
|
|
_threadCount = cpuId.Sum(x => x.Length);
|
|
|
|
// Check if processor has MSRs.
|
|
HasModelSpecificRegisters = cpuId[0][0].Data.GetLength(0) > 1 && (cpuId[0][0].Data[1, 3] & 0x20) != 0;
|
|
|
|
// Check if processor has a TSC.
|
|
HasTimeStampCounter = cpuId[0][0].Data.GetLength(0) > 1 && (cpuId[0][0].Data[1, 3] & 0x10) != 0;
|
|
|
|
// Check if processor supports an invariant TSC.
|
|
_isInvariantTimeStampCounter = cpuId[0][0].ExtData.GetLength(0) > 7 && (cpuId[0][0].ExtData[7, 3] & 0x100) != 0;
|
|
|
|
_totalLoad = _coreCount > 1 ? new Sensor("CPU Total", 0, SensorType.Load, this, settings) : null;
|
|
_maxLoad = _coreCount > 1 ? new Sensor("CPU Core Max", 1, SensorType.Load, this, settings) : null;
|
|
|
|
_cpuLoad = Measure(startupTrace, "GenericCpu.CpuLoad", () => new CpuLoad(cpuId), load => load.IsAvailable ? "Available" : "Unavailable");
|
|
if (_cpuLoad.IsAvailable)
|
|
{
|
|
_threadLoads = new Sensor[_threadCount];
|
|
for (int coreIdx = 0; coreIdx < cpuId.Length; coreIdx++)
|
|
{
|
|
for (int threadIdx = 0; threadIdx < cpuId[coreIdx].Length; threadIdx++)
|
|
{
|
|
int thread = cpuId[coreIdx][threadIdx].Thread;
|
|
if (thread < _threadLoads.Length)
|
|
{
|
|
// Some cores may have 2 threads while others have only one (e.g. P-cores vs E-cores on Intel 12th gen).
|
|
string sensorName = CoreString(coreIdx) + (cpuId[coreIdx].Length > 1 ? $" Thread #{threadIdx + 1}" : string.Empty);
|
|
_threadLoads[thread] = new Sensor(sensorName, thread + 2, SensorType.Load, this, settings);
|
|
|
|
ActivateSensor(_threadLoads[thread]);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (_totalLoad != null)
|
|
{
|
|
ActivateSensor(_totalLoad);
|
|
}
|
|
|
|
if (_maxLoad != null)
|
|
{
|
|
ActivateSensor(_maxLoad);
|
|
}
|
|
}
|
|
|
|
if (HasTimeStampCounter)
|
|
{
|
|
GroupAffinity affinity = cpuId[0][0].Affinity;
|
|
if (ShouldDeferTscEstimation(settings))
|
|
{
|
|
// The estimate is only a seed for the first Update() (~1 s out) and is then self-corrected from real
|
|
// TSC deltas, so the ~25 ms measurement window can run off the startup critical path.
|
|
startupTrace?.Skip("GenericCpu.EstimateTimeStampCounterFrequency", "Deferred to background.");
|
|
_timeStampCounterFrequencyTask = Task.Run(() =>
|
|
{
|
|
try
|
|
{
|
|
EstimateAndStoreTimeStampCounterFrequency(affinity, null);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.WriteLine($"Deferred TSC frequency estimation failed: {ex}");
|
|
}
|
|
});
|
|
}
|
|
else
|
|
{
|
|
EstimateAndStoreTimeStampCounterFrequency(affinity, startupTrace);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the CPUID.
|
|
/// </summary>
|
|
public CpuId[][] CpuId => _cpuId;
|
|
|
|
public override HardwareType HardwareType => HardwareType.Cpu;
|
|
|
|
public bool HasModelSpecificRegisters { get; }
|
|
|
|
public bool HasTimeStampCounter { get; }
|
|
|
|
/// <summary>
|
|
/// Gets the CPU index.
|
|
/// </summary>
|
|
public int Index { get; }
|
|
|
|
public double TimeStampCounterFrequency
|
|
{
|
|
get
|
|
{
|
|
lock (_timeStampCounterFrequencyLock)
|
|
return _timeStampCounterFrequency;
|
|
}
|
|
private set
|
|
{
|
|
lock (_timeStampCounterFrequencyLock)
|
|
_timeStampCounterFrequency = value;
|
|
}
|
|
}
|
|
|
|
protected string CoreString(int i)
|
|
{
|
|
if (_coreCount == 1)
|
|
return "CPU Core";
|
|
|
|
return "CPU Core #" + (i + 1);
|
|
}
|
|
|
|
private static Identifier CreateIdentifier(Vendor vendor, int processorIndex)
|
|
{
|
|
string s = vendor switch
|
|
{
|
|
Vendor.AMD => "amdcpu",
|
|
Vendor.Intel => "intelcpu",
|
|
_ => "genericcpu"
|
|
};
|
|
|
|
return new Identifier(s, processorIndex.ToString(CultureInfo.InvariantCulture));
|
|
}
|
|
|
|
private static void Measure(HardwareStartupTrace startupTrace, string phase, Action action)
|
|
{
|
|
if (startupTrace != null)
|
|
startupTrace.Measure(phase, action);
|
|
else
|
|
action();
|
|
}
|
|
|
|
private static T Measure<T>(HardwareStartupTrace startupTrace, string phase, Func<T> action, Func<T, string> getDetail)
|
|
{
|
|
return startupTrace != null ? startupTrace.Measure(phase, action, getDetail) : action();
|
|
}
|
|
|
|
private static void EstimateTimeStampCounterFrequency(out double frequency, out double error)
|
|
{
|
|
// preload the function
|
|
EstimateTimeStampCounterFrequency(0, out double f, out double e);
|
|
EstimateTimeStampCounterFrequency(0, out f, out e);
|
|
|
|
// estimate the frequency
|
|
error = double.MaxValue;
|
|
frequency = 0;
|
|
for (int i = 0; i < 5; i++)
|
|
{
|
|
EstimateTimeStampCounterFrequency(0.025, out f, out e);
|
|
if (e < error)
|
|
{
|
|
error = e;
|
|
frequency = f;
|
|
}
|
|
|
|
if (error < 1e-4)
|
|
break;
|
|
}
|
|
}
|
|
|
|
private static void EstimateTimeStampCounterFrequency(double timeWindow, out double frequency, out double error)
|
|
{
|
|
long ticks = (long)(timeWindow * Stopwatch.Frequency);
|
|
|
|
long timeBegin = Stopwatch.GetTimestamp() + (long)Math.Ceiling(0.001 * ticks);
|
|
long timeEnd = timeBegin + ticks;
|
|
|
|
while (Stopwatch.GetTimestamp() < timeBegin)
|
|
{ }
|
|
|
|
ulong countBegin = OpCode.Rdtsc();
|
|
long afterBegin = Stopwatch.GetTimestamp();
|
|
|
|
while (Stopwatch.GetTimestamp() < timeEnd)
|
|
{ }
|
|
|
|
ulong countEnd = OpCode.Rdtsc();
|
|
long afterEnd = Stopwatch.GetTimestamp();
|
|
|
|
double delta = timeEnd - timeBegin;
|
|
frequency = 1e-6 * ((double)(countEnd - countBegin) * Stopwatch.Frequency) / delta;
|
|
|
|
double beginError = (afterBegin - timeBegin) / delta;
|
|
double endError = (afterEnd - timeEnd) / delta;
|
|
error = beginError + endError;
|
|
}
|
|
|
|
private void EstimateAndStoreTimeStampCounterFrequency(GroupAffinity affinity, HardwareStartupTrace startupTrace)
|
|
{
|
|
GroupAffinity previousAffinity = ThreadAffinity.Set(affinity);
|
|
try
|
|
{
|
|
double frequency = 0;
|
|
double error = 0;
|
|
Measure(startupTrace,
|
|
"GenericCpu.EstimateTimeStampCounterFrequency",
|
|
() => EstimateTimeStampCounterFrequency(out frequency, out error));
|
|
StoreTimeStampCounterFrequency(frequency, error);
|
|
}
|
|
finally
|
|
{
|
|
ThreadAffinity.Set(previousAffinity);
|
|
}
|
|
}
|
|
|
|
private void StoreTimeStampCounterFrequency(double frequency, double error)
|
|
{
|
|
lock (_timeStampCounterFrequencyLock)
|
|
{
|
|
_estimatedTimeStampCounterFrequency = frequency;
|
|
_estimatedTimeStampCounterFrequencyError = error;
|
|
_timeStampCounterFrequency = frequency;
|
|
}
|
|
}
|
|
|
|
private static bool ShouldDeferTscEstimation(ISettings settings)
|
|
{
|
|
string environmentValue = Environment.GetEnvironmentVariable(DeferTscEstimationEnvironmentVariable);
|
|
if (!string.IsNullOrWhiteSpace(environmentValue))
|
|
return IsTruthy(environmentValue);
|
|
|
|
return IsTruthy(settings.GetValue(DeferTscEstimationSetting, "false"));
|
|
}
|
|
|
|
private static bool IsTruthy(string value)
|
|
{
|
|
return value.Equals("1", StringComparison.OrdinalIgnoreCase)
|
|
|| value.Equals("true", StringComparison.OrdinalIgnoreCase)
|
|
|| value.Equals("yes", StringComparison.OrdinalIgnoreCase)
|
|
|| value.Equals("on", StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
|
|
public override string GetReport()
|
|
{
|
|
StringBuilder r = new();
|
|
double estimatedTimeStampCounterFrequency;
|
|
double estimatedTimeStampCounterFrequencyError;
|
|
double timeStampCounterFrequency;
|
|
|
|
lock (_timeStampCounterFrequencyLock)
|
|
{
|
|
estimatedTimeStampCounterFrequency = _estimatedTimeStampCounterFrequency;
|
|
estimatedTimeStampCounterFrequencyError = _estimatedTimeStampCounterFrequencyError;
|
|
timeStampCounterFrequency = _timeStampCounterFrequency;
|
|
}
|
|
|
|
switch (_vendor)
|
|
{
|
|
case Vendor.AMD:
|
|
r.AppendLine("AMD CPU");
|
|
break;
|
|
case Vendor.Intel:
|
|
r.AppendLine("Intel CPU");
|
|
break;
|
|
default:
|
|
r.AppendLine("Generic CPU");
|
|
break;
|
|
}
|
|
|
|
r.AppendLine();
|
|
r.AppendFormat("Name: {0}{1}", _name, Environment.NewLine);
|
|
r.AppendFormat("Number of Cores: {0}{1}", _coreCount, Environment.NewLine);
|
|
r.AppendFormat("Threads per Core: {0}{1}", _cpuId[0].Length, Environment.NewLine);
|
|
r.AppendLine(string.Format(CultureInfo.InvariantCulture, "Timer Frequency: {0} MHz", Stopwatch.Frequency * 1e-6));
|
|
r.AppendLine("Time Stamp Counter: " + (HasTimeStampCounter ? _isInvariantTimeStampCounter ? "Invariant" : "Not Invariant" : "None"));
|
|
r.AppendLine(string.Format(CultureInfo.InvariantCulture, "Estimated Time Stamp Counter Frequency: {0} MHz", Math.Round(estimatedTimeStampCounterFrequency * 100) * 0.01));
|
|
r.AppendLine(string.Format(CultureInfo.InvariantCulture,
|
|
"Estimated Time Stamp Counter Frequency Error: {0} Mhz",
|
|
Math.Round(estimatedTimeStampCounterFrequency * estimatedTimeStampCounterFrequencyError * 1e5) * 1e-5));
|
|
|
|
r.AppendLine(string.Format(CultureInfo.InvariantCulture, "Time Stamp Counter Frequency: {0} MHz", Math.Round(timeStampCounterFrequency * 100) * 0.01));
|
|
r.AppendLine();
|
|
|
|
return r.ToString();
|
|
}
|
|
|
|
public override void Close()
|
|
{
|
|
try
|
|
{
|
|
_timeStampCounterFrequencyTask?.Wait(TimeSpan.FromMilliseconds(100));
|
|
}
|
|
catch (AggregateException ex)
|
|
{
|
|
Debug.WriteLine($"Deferred TSC frequency estimation failed while closing: {ex}");
|
|
}
|
|
|
|
base.Close();
|
|
}
|
|
|
|
public override void Update()
|
|
{
|
|
if (HasTimeStampCounter && _isInvariantTimeStampCounter)
|
|
{
|
|
// make sure always the same thread is used
|
|
GroupAffinity previousAffinity = ThreadAffinity.Set(_cpuId[0][0].Affinity);
|
|
|
|
// read time before and after getting the TSC to estimate the error
|
|
long firstTime = Stopwatch.GetTimestamp();
|
|
ulong timeStampCount = OpCode.Rdtsc();
|
|
long time = Stopwatch.GetTimestamp();
|
|
|
|
// restore the thread affinity mask
|
|
ThreadAffinity.Set(previousAffinity);
|
|
|
|
double delta = (double)(time - _lastTime) / Stopwatch.Frequency;
|
|
double error = (double)(time - firstTime) / Stopwatch.Frequency;
|
|
|
|
// only use data if they are measured accurate enough (max 0.1ms delay)
|
|
if (error < 0.0001)
|
|
{
|
|
// ignore the first reading because there are no initial values
|
|
// ignore readings with too large or too small time window
|
|
if (_lastTime != 0 && delta is > 0.5 and < 2)
|
|
{
|
|
// update the TSC frequency with the new value
|
|
TimeStampCounterFrequency = (timeStampCount - _lastTimeStampCount) / (1e6 * delta);
|
|
}
|
|
|
|
_lastTimeStampCount = timeStampCount;
|
|
_lastTime = time;
|
|
}
|
|
}
|
|
|
|
if (_cpuLoad.IsAvailable)
|
|
{
|
|
_cpuLoad.Update();
|
|
|
|
float maxLoad = 0;
|
|
if (_threadLoads != null)
|
|
{
|
|
for (int i = 0; i < _threadLoads.Length; i++)
|
|
{
|
|
if (_threadLoads[i] != null)
|
|
{
|
|
_threadLoads[i].Value = (float)_cpuLoad.GetThreadLoad(i);
|
|
maxLoad = Math.Max(maxLoad, _threadLoads[i].Value ?? 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (_totalLoad != null)
|
|
_totalLoad.Value = (float)_cpuLoad.GetTotalLoad();
|
|
|
|
if (_maxLoad != null)
|
|
_maxLoad.Value = maxLoad;
|
|
}
|
|
}
|
|
}
|