// 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 and Contributors. // All Rights Reserved. using System; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Text; namespace LibreHardwareMonitor.Hardware.Cpu; public class GenericCpu : Hardware { 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 readonly double _estimatedTimeStampCounterFrequency; private readonly double _estimatedTimeStampCounterFrequencyError; private readonly bool _isInvariantTimeStampCounter; private readonly Sensor[] _threadLoads; private readonly Sensor _totalLoad; private readonly Sensor _maxLoad; private readonly Vendor _vendor; private long _lastTime; private ulong _lastTimeStampCount; public GenericCpu(int processorIndex, CpuId[][] cpuId, ISettings settings) : 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 = new CpuLoad(cpuId); 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 previousAffinity = ThreadAffinity.Set(cpuId[0][0].Affinity); EstimateTimeStampCounterFrequency(out _estimatedTimeStampCounterFrequency, out _estimatedTimeStampCounterFrequencyError); ThreadAffinity.Set(previousAffinity); } else { _estimatedTimeStampCounterFrequency = 0; } TimeStampCounterFrequency = _estimatedTimeStampCounterFrequency; } /// /// Gets the CPUID. /// public CpuId[][] CpuId => _cpuId; public override HardwareType HardwareType => HardwareType.Cpu; public bool HasModelSpecificRegisters { get; } public bool HasTimeStampCounter { get; } /// /// Gets the CPU index. /// public int Index { get; } public double TimeStampCounterFrequency { get; private set; } 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 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 static void AppendMsrData(StringBuilder r, uint msr, GroupAffinity affinity) { if (Ring0.ReadMsr(msr, out uint eax, out uint edx, affinity)) { r.Append(" "); r.Append(msr.ToString("X8", CultureInfo.InvariantCulture)); r.Append(" "); r.Append(edx.ToString("X8", CultureInfo.InvariantCulture)); r.Append(" "); r.Append(eax.ToString("X8", CultureInfo.InvariantCulture)); r.AppendLine(); } } protected virtual uint[] GetMsrs() { return null; } public override string GetReport() { StringBuilder r = new(); 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(); uint[] msrArray = GetMsrs(); if (msrArray is { Length: > 0 }) { for (int i = 0; i < _cpuId.Length; i++) { r.AppendLine("MSR Core #" + (i + 1)); r.AppendLine(); r.AppendLine(" MSR EDX EAX"); foreach (uint msr in msrArray) AppendMsrData(r, msr, _cpuId[i][0].Affinity); r.AppendLine(); } } return r.ToString(); } 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 = _cpuLoad.GetThreadLoad(i); maxLoad = Math.Max(maxLoad, _threadLoads[i].Value ?? 0); } } } if (_totalLoad != null) _totalLoad.Value = _cpuLoad.GetTotalLoad(); if (_maxLoad != null) _maxLoad.Value = maxLoad; } } }