dorisoy_HardwareMonitor/LibreHardwareMonitorLib/Hardware/Cpu/GenericCpu.cs
// 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.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;

        private readonly Sensor[] _coreLoads;
        private readonly CpuLoad _cpuLoad;
        private readonly double _estimatedTimeStampCounterFrequency;
        private readonly double _estimatedTimeStampCounterFrequencyError;
        private readonly bool _isInvariantTimeStampCounter;
        private readonly Sensor _totalLoad;
        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;

            // check if processor has MSRs
            if (cpuId[0][0].Data.GetLength(0) > 1 && (cpuId[0][0].Data[1, 3] & 0x20) != 0)
                HasModelSpecificRegisters = true;
            else
                HasModelSpecificRegisters = false;

            // check if processor has a TSC
            if (cpuId[0][0].Data.GetLength(0) > 1 && (cpuId[0][0].Data[1, 3] & 0x10) != 0)
                HasTimeStampCounter = true;
            else
                HasTimeStampCounter = false;

            // check if processor supports an invariant TSC
            if (cpuId[0][0].ExtData.GetLength(0) > 7 && (cpuId[0][0].ExtData[7, 3] & 0x100) != 0)
                _isInvariantTimeStampCounter = true;
            else
                _isInvariantTimeStampCounter = false;

            _totalLoad = _coreCount > 1 ? new Sensor("CPU Total", 0, SensorType.Load, this, settings) : null;

            _coreLoads = new Sensor[_coreCount];
            for (int i = 0; i < _coreLoads.Length; i++)
                _coreLoads[i] = new Sensor(CoreString(i), i + 1, SensorType.Load, this, settings);

            _cpuLoad = new CpuLoad(cpuId);
            if (_cpuLoad.IsAvailable)
            {
                foreach (Sensor sensor in _coreLoads)
                {
                    ActivateSensor(sensor);
                }

                if (_totalLoad != null)
                {
                    ActivateSensor(_totalLoad);
                }
            }

            if (HasTimeStampCounter)
            {
                GroupAffinity previousAffinity = ThreadAffinity.Set(cpuId[0][0].Affinity);
                EstimateTimeStampCounterFrequency(out _estimatedTimeStampCounterFrequency, out _estimatedTimeStampCounterFrequencyError);
                ThreadAffinity.Set(previousAffinity);
            }
            else
            {
                _estimatedTimeStampCounterFrequency = 0;
            }

            TimeStampCounterFrequency = _estimatedTimeStampCounterFrequency;
        }

        /// <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; 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;
            switch (vendor)
            {
                case Vendor.AMD:
                    s = "amdcpu";
                    break;
                case Vendor.Intel:
                    s = "intelcpu";
                    break;
                default:
                    s = "genericcpu";
                    break;
            }

            return new Identifier(s, processorIndex.ToString(CultureInfo.InvariantCulture));
        }

        private 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 != null && msrArray.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 > 0.5 && delta < 2)
                    {
                        // update the TSC frequency with the new value
                        TimeStampCounterFrequency = (timeStampCount - _lastTimeStampCount) / (1e6 * delta);
                    }

                    _lastTimeStampCount = timeStampCount;
                    _lastTime = time;
                }
            }

            if (_cpuLoad.IsAvailable)
            {
                _cpuLoad.Update();
                for (int i = 0; i < _coreLoads.Length; i++)
                    _coreLoads[i].Value = _cpuLoad.GetCoreLoad(i);

                if (_totalLoad != null)
                    _totalLoad.Value = _cpuLoad.GetTotalLoad();
            }
        }
    }
}
