Files
KinginfoGateway/src/Admin/ThingsGateway.NewLife.X/Common/MachineInfo.cs

1399 lines
48 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Net.NetworkInformation;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using ThingsGateway.NewLife.Collections;
using ThingsGateway.NewLife.Data;
using ThingsGateway.NewLife.Log;
using ThingsGateway.NewLife.Model;
using ThingsGateway.NewLife.Reflection;
using ThingsGateway.NewLife.Serialization;
using ThingsGateway.NewLife.Windows;
using System.Diagnostics;
using System.Runtime;
#if NETFRAMEWORK
using System.Management;
using Microsoft.VisualBasic.Devices;
#endif
#if NETFRAMEWORK || NET6_0_OR_GREATER
using Microsoft.Win32;
#endif
namespace ThingsGateway.NewLife;
/// <summary>机器信息接口</summary>
/// <remarks>用于扩展MachineInfo功能具体应用自定义各字段获取方式</remarks>
public interface IMachineInfo
{
/// <summary>初始化静态数据</summary>
void Init(MachineInfo info);
/// <summary>刷新动态数据</summary>
void Refresh(MachineInfo info);
}
/// <summary>机器信息</summary>
/// <remarks>
/// 文档 https://newlifex.com/core/machine_info
///
/// 刷新信息成本较高,建议采用单例模式
/// </remarks>
public class MachineInfo
{
#region
/// <summary>系统名称</summary>
[DisplayName("系统名称")]
public String? OSName { get; set; }
/// <summary>系统版本</summary>
[DisplayName("系统版本")]
public String? OSVersion { get; set; }
/// <summary>产品名称</summary>
[DisplayName("产品名称")]
public String? Product { get; set; }
/// <summary>制造商</summary>
[DisplayName("制造商")]
public String? Vendor { get; set; }
/// <summary>处理器型号</summary>
[DisplayName("处理器型号")]
public String? Processor { get; set; }
///// <summary>处理器序列号。PC处理器序列号绝大部分重复实际存储处理器的其它信息</summary>
//public String CpuID { get; set; }
/// <summary>硬件唯一标识。取主板编码,部分品牌存在重复</summary>
[DisplayName("硬件唯一标识")]
public String? UUID { get; set; }
/// <summary>软件唯一标识。系统标识操作系统重装后更新Linux系统的machine_idAndroid的android_idGhost系统存在重复</summary>
[DisplayName("软件唯一标识")]
public String? Guid { get; set; }
/// <summary>计算机序列号。适用于品牌机,跟笔记本标签显示一致</summary>
[DisplayName("计算机序列号")]
public String? Serial { get; set; }
/// <summary>主板。序列号或家族信息</summary>
[DisplayName("主板")]
public String? Board { get; set; }
/// <summary>磁盘序列号</summary>
[DisplayName("磁盘序列号")]
public String? DiskID { get; set; }
/// <summary>内存总量。单位MB</summary>
[DisplayName("内存总量")]
public UInt64 Memory { get; set; }
/// <summary>可用内存。单位MB</summary>
[DisplayName("可用内存")]
public UInt64 AvailableMemory { get; set; }
/// <summary>CPU占用率</summary>
[DisplayName("CPU占用率")]
public Double CpuRate { get; set; }
/// <summary>网络上行速度。字节每秒初始化后首次读取为0</summary>
[DisplayName("网络上行速度")]
public UInt64 UplinkSpeed { get; set; }
/// <summary>网络下行速度。字节每秒初始化后首次读取为0</summary>
[DisplayName("网络下行速度")]
public UInt64 DownlinkSpeed { get; set; }
/// <summary>温度。单位度</summary>
[DisplayName("温度")]
public Double Temperature { get; set; }
/// <summary>电池剩余。小于1的小数常用百分比表示</summary>
[DisplayName("电池剩余")]
public Double Battery { get; set; }
#if NET6_0_OR_GREATER
#region GC与进程内存信息
/// <summary>GC 认为“内存吃紧”的阈值。单位MB</summary>
[DisplayName("GC高内存阈值")]
public UInt64 HighMemoryLoadThreshold { get; set; }
/// <summary>GC 可用内存上限。单位MB</summary>
[DisplayName("GC可用内存上限")]
public UInt64 TotalAvailableMemory { get; set; }
/// <summary>当前托管堆容量。单位MB</summary>
[DisplayName("托管堆容量")]
public UInt64 HeapSize { get; set; }
/// <summary>托管堆已用内存。单位MB</summary>
[DisplayName("托管堆已用")]
public UInt64 TotalMemory { get; set; }
/// <summary>托管堆碎片大小。单位MB</summary>
[DisplayName("托管堆碎片")]
public UInt64 FragmentedBytes { get; set; }
/// <summary>GC识别可用内存。单位MB</summary>
[DisplayName("GC识别可用内存")]
public UInt64 GCAvailableMemory { get; set; }
/// <summary>GC 已提交的内存。单位MB</summary>
[DisplayName("GC已提交内存")]
public UInt64 CommittedBytes { get; set; }
/// <summary>GC 累计分配的托管内存。单位MB</summary>
[DisplayName("GC累计分配")]
public UInt64 TotalAllocatedBytes { get; set; }
#if NET8_0_OR_GREATER
/// <summary>GC 暂停累计时间。单位:毫秒</summary>
[DisplayName("GC累计暂停时间")]
public UInt64 TotalPauseDurationMs { get; set; }
#endif
/// <summary>GC 代0收集次数</summary>
[DisplayName("GC Gen0 次数")]
public Int32 GcGen0Count { get; set; }
/// <summary>GC 代1收集次数</summary>
[DisplayName("GC Gen1 次数")]
public Int32 GcGen1Count { get; set; }
/// <summary>GC 代2收集次数</summary>
[DisplayName("GC Gen2 次数")]
public Int32 GcGen2Count { get; set; }
/// <summary>Server GC 是否启用</summary>
[DisplayName("是否使用Server GC")]
public Boolean IsServerGC { get; set; }
/// <summary>GC 延迟模式</summary>
[DisplayName("GC延迟模式")]
public GCLatencyMode? GCLatencyMode { get; set; }
/// <summary>GC 固定对象数</summary>
[DisplayName("固定对象数")]
public Int64 PinnedObjectsCount { get; set; }
/// <summary>终结队列挂起对象数</summary>
[DisplayName("终结挂起数")]
public Int64 FinalizationPendingCount { get; set; }
#endregion
#region
/// <summary>进程虚拟内存使用量。单位MB</summary>
[DisplayName("虚拟内存")]
public UInt64 VirtualMemory { get; set; }
/// <summary>进程私有内存使用量。单位MB</summary>
[DisplayName("私有内存")]
public UInt64 PrivateMemory { get; set; }
/// <summary>进程峰值工作集。单位MB</summary>
[DisplayName("峰值工作集")]
public UInt64 PeakWorkingSet { get; set; }
/// <summary>进程当前工作集。单位MB</summary>
[DisplayName("当前工作集")]
public UInt64 WorkingSet { get; set; }
#endregion
#endif
#endregion
#region
/// <summary>当前机器信息。默认null在RegisterAsync后才能使用</summary>
public static MachineInfo? Current { get; set; }
/// <summary>机器信息提供者。外部实现可修改部分行为</summary>
public static IMachineInfo? Provider { get; set; }
private static MachineInfo Register()
{
var set = Setting.Current;
var dataPath = set.DataPath;
if (dataPath.IsNullOrEmpty()) dataPath = "Data";
// 文件缓存加快机器信息获取。在Linux下可能StarAgent以root权限写入缓存文件其它应用以普通用户访问
var file = Path.GetTempPath().CombinePath("machine_info.json");
var file2 = dataPath.CombinePath("machine_info.json").GetBasePath();
var json = string.Empty;
if (Current == null)
{
var f = file;
if (!File.Exists(f)) f = file2;
if (File.Exists(f))
{
try
{
//XTrace.WriteLine("Load MachineInfo {0}", f);
json = File.ReadAllText(f);
Current = json.ToJsonEntity<MachineInfo>();
}
catch (Exception ex)
{
if (XTrace.Log.Level <= LogLevel.Debug) XTrace.WriteException(ex);
}
}
}
var mi = Current ?? new MachineInfo();
mi.Init();
Current = mi;
// 注册到对象容器
ObjectContainer.Current.AddSingleton(mi);
try
{
var json2 = mi.ToJson(true);
if (json != json2)
{
File.WriteAllText(file2.EnsureDirectory(true), json2);
File.WriteAllText(file.EnsureDirectory(true), json2);
}
}
catch (Exception ex)
{
if (XTrace.Log.Level <= LogLevel.Debug) XTrace.WriteException(ex);
}
return mi;
}
/// <summary>获取当前信息,如果未设置则等待异步注册结果</summary>
/// <returns></returns>
public static MachineInfo GetCurrent() => Current ?? Register();
/// <summary>从对象容器中获取一个已注册机器信息实例</summary>
/// <returns></returns>
public static MachineInfo? Resolve() => ObjectContainer.Current.GetService<MachineInfo>();
#endregion
#region
/// <summary>初始化静态数据。可能是实例化后执行也可能是Json反序列化后执行</summary>
public void Init()
{
var osv = Environment.OSVersion;
if (OSVersion.IsNullOrEmpty()) OSVersion = osv.Version + string.Empty;
if (OSName.IsNullOrEmpty()) OSName = (osv + "").TrimStart("Microsoft").TrimEnd(OSVersion).Trim();
if (Guid.IsNullOrEmpty()) Guid = string.Empty;
try
{
#if NET6_0_OR_GREATER
if (OperatingSystem.IsWindows())
LoadWindowsInfo();
else if (OperatingSystem.IsLinux())
LoadLinuxInfo();
else if (OperatingSystem.IsMacOS())
LoadMacInfo();
#else
if (Runtime.Windows)
LoadWindowsInfo();
else if (Runtime.Linux)
LoadLinuxInfo();
#endif
Provider?.Init(this);
}
catch (Exception ex)
{
if (XTrace.Log.Level <= LogLevel.Debug) XTrace.WriteException(ex);
}
// 裁剪不可见字符,顺带去掉两头空白
OSName = OSName.TrimInvisible()?.Trim();
OSVersion = OSVersion.TrimInvisible()?.Trim();
Product = Product.TrimInvisible()?.Trim();
Processor = Processor.TrimInvisible()?.Trim();
UUID = UUID.TrimInvisible()?.Trim();
Guid = Guid.TrimInvisible()?.Trim();
Serial = Serial.TrimInvisible()?.Trim();
Board = Board.TrimInvisible()?.Trim();
DiskID = DiskID.TrimInvisible()?.Trim();
// 无法读取系统标识时随机生成一个guid借助文件缓存确保其不变
if (Guid.IsNullOrEmpty()) Guid = "0-" + System.Guid.NewGuid().ToString();
if (UUID.IsNullOrEmpty()) UUID = "0-" + System.Guid.NewGuid().ToString();
try
{
Refresh();
}
catch (Exception ex)
{
if (XTrace.Log.Level <= LogLevel.Debug) XTrace.WriteException(ex);
}
}
#if NET6_0_OR_GREATER
[SupportedOSPlatform("windows")]
#endif
private void LoadWindowsInfo()
{
var str = string.Empty;
// 从注册表读取 MachineGuid
#if NETFRAMEWORK || NET6_0_OR_GREATER
var reg = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Cryptography");
if (reg != null) str = reg.GetValue("MachineGuid") + string.Empty;
if (str.IsNullOrEmpty())
{
reg = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64);
reg = reg?.OpenSubKey(@"SOFTWARE\Microsoft\Cryptography");
if (reg != null) str = reg.GetValue("MachineGuid") + string.Empty;
}
if (!str.IsNullOrEmpty()) Guid = str;
reg = Registry.LocalMachine.OpenSubKey(@"SYSTEM\HardwareConfig");
if (reg != null)
{
str = (reg.GetValue("LastConfig") + "")?.Trim('{', '}').ToUpper();
// UUID取不到时返回 FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF
if (!str.IsNullOrEmpty() && !str.EqualIgnoreCase("FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF")) UUID = str;
}
reg = Registry.LocalMachine.OpenSubKey(@"HARDWARE\DESCRIPTION\System\BIOS");
reg ??= Registry.LocalMachine.OpenSubKey(@"SYSTEM\HardwareConfig\Current");
if (reg != null)
{
Product = (reg.GetValue("SystemProductName") + "").Replace("System Product Name", null);
if (Product.IsNullOrEmpty()) Product = reg.GetValue("BaseBoardProduct") + string.Empty;
Vendor = reg.GetValue("SystemManufacturer") + string.Empty;
if (Vendor.IsNullOrEmpty()) Vendor = reg.GetValue("ASUSTeK COMPUTER INC.") + string.Empty;
}
reg = Registry.LocalMachine.OpenSubKey(@"HARDWARE\DESCRIPTION\System\CentralProcessor\0");
if (reg != null) Processor = reg.GetValue("ProcessorNameString") + string.Empty;
// 旧版系统如win2008没有UUID的注册表项需要用wmic查询。也可能因为过去的某个BUG导致GUID跟UUID相等
if (UUID.IsNullOrEmpty() || UUID == Guid || Vendor.IsNullOrEmpty())
{
var csproduct = ReadWmic("csproduct", "Name", "UUID", "Vendor");
if (csproduct != null)
{
if (csproduct.TryGetValue("Name", out str) && !str.IsNullOrEmpty() && Product.IsNullOrEmpty()) Product = str;
if (csproduct.TryGetValue("UUID", out str) && !str.IsNullOrEmpty()) UUID = str;
if (csproduct.TryGetValue("Vendor", out str) && !str.IsNullOrEmpty()) Vendor = str;
}
}
#else
str = "reg".Execute(@"query HKLM\SOFTWARE\Microsoft\Cryptography /v MachineGuid", 0, false);
if (!str.IsNullOrEmpty() && str.Contains("REG_SZ")) Guid = str.Substring("REG_SZ", null).Trim();
var csproduct = ReadWmic("csproduct", "Name", "UUID", "Vendor");
if (csproduct != null)
{
if (csproduct.TryGetValue("Name", out str)) Product = str;
if (csproduct.TryGetValue("UUID", out str)) UUID = str;
if (csproduct.TryGetValue("Vendor", out str)) Vendor = str;
}
#endif
// 获取内存大小
#if NETFRAMEWORK || WINDOWS
{
var ci = new Microsoft.VisualBasic.Devices.ComputerInfo();
Memory = (ulong)(ci.TotalPhysicalMemory / 1024.0);
}
#endif
// 获取操作系统名称和版本
#if NETFRAMEWORK
try
{
var ci = new Microsoft.VisualBasic.Devices.ComputerInfo();
// 系统名取WMI可能出错
OSName = ci.OSFullName?.Replace("®", null).TrimStart("Microsoft").Trim();
OSVersion = ci.OSVersion;
}
catch
{
try
{
var reg2 = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion");
if (reg2 != null)
{
OSName = reg2.GetValue("ProductName") + string.Empty;
OSVersion = reg2.GetValue("ReleaseId") + string.Empty;
}
}
catch (Exception ex)
{
if (XTrace.Log.Level <= LogLevel.Debug) XTrace.WriteException(ex);
}
}
//#elif NET6_0_OR_GREATER
// OSName = GetInfo("Win32_OperatingSystem", "Caption")?.TrimStart("Microsoft").Trim();
// OSVersion = GetInfo("Win32_OperatingSystem", "Version");
#else
var os = ReadWmic("os", "Caption", "Version");
if (os == null || os.Count == 0)
{
os = ReadPowerShell("Get-WmiObject Win32_OperatingSystem | Select-Object Caption, Version | ConvertTo-Json");
}
if (os is { Count: > 0 })
{
if (os.TryGetValue("Caption", out str)) OSName = str.TrimStart("Microsoft").Trim();
if (os.TryGetValue("Version", out str)) OSVersion = str;
}
#endif
#if NETFRAMEWORK
//Processor = GetInfo("Win32_Processor", "Name");
//CpuID = GetInfo("Win32_Processor", "ProcessorId");
//var uuid = GetInfo("Win32_ComputerSystemProduct", "UUID");
//Product = GetInfo("Win32_ComputerSystemProduct", "Name");
DiskID = GetInfo("Win32_DiskDrive where mediatype=\"Fixed hard disk media\"", "SerialNumber");
var sn = GetInfo("Win32_BIOS", "SerialNumber");
if (!sn.IsNullOrEmpty() && !sn.EqualIgnoreCase("System Serial Number")) Serial = sn;
Board = GetInfo("Win32_BaseBoard", "SerialNumber");
#else
var disk = ReadWmic("diskdrive where mediatype=\"Fixed hard disk media\"", "serialnumber");
if (disk != null)
{
if (disk.TryGetValue("serialnumber", out str)) DiskID = str?.Trim();
}
var sn = ReadWmic("bios", "serialnumber");
if (sn != null)
{
if (sn.TryGetValue("serialnumber", out str) && !str.EqualIgnoreCase("System Serial Number")) Serial = str?.Trim();
}
var board = ReadWmic("baseboard", "serialnumber");
if (board != null)
{
if (board.TryGetValue("serialnumber", out str)) Board = str?.Trim();
}
//// 不要在刷新里面取CPU负载因为运行wmic会导致CPU负载很不准确影响测量
//var cpu = ReadWmic("cpu", "Name", "ProcessorId", "LoadPercentage");
//if (cpu != null)
//{
// if (cpu.TryGetValue("Name", out str)) Processor = str;
// //if (cpu.TryGetValue("ProcessorId", out str)) CpuID = str;
// if (cpu.TryGetValue("LoadPercentage", out str)) CpuRate = (Single)(str.ToDouble() / 100);
//}
#endif
#if !NETFRAMEWORK
if (OSName.IsNullOrEmpty())
OSName = RuntimeInformation.OSDescription.TrimStart("Microsoft").Trim();
if (OSVersion.IsNullOrEmpty())
OSVersion = Environment.OSVersion.Version.ToString();
#endif
}
private void LoadLinuxInfo()
{
var str = GetLinuxName();
if (!str.IsNullOrEmpty()) OSName = str;
var device = ReadDeviceInfo();
if (device.TryGetValue("Platform", out str))
OSName = str;
if (device.TryGetValue("Version", out str))
OSVersion = str;
// 树莓派的Hardware无法区分P0/P4
var dic = ReadInfo("/proc/cpuinfo");
if (dic != null)
{
if (dic.TryGetValue("Hardware", out str) ||
dic.TryGetValue("cpu model", out str) ||
dic.TryGetValue("model name", out str))
Processor = str?.TrimStart("vendor ");
if (device.TryGetValue("Product", out str))
Product = str;
else if (dic.TryGetValue("Model", out str))
Product = str;
if (dic.TryGetValue("vendor_id", out str))
Vendor = str;
//if (device.TryGetValue("Fingerprint", out str) && !str.IsNullOrEmpty())
// CpuID = str;
if (dic.TryGetValue("Serial", out str) && !str.IsNullOrEmpty() && !str.Trim('0').IsNullOrEmpty())
UUID = str;
}
var mid = "/etc/machine-id";
if (!File.Exists(mid)) mid = "/var/lib/dbus/machine-id";
if (TryRead(mid, out var value))
Guid = value;
else if (device.TryGetValue("android_id", out str) && !str.IsNullOrEmpty() && str != "unknown")
Guid = str;
//else if (android.TryGetValue("Id", out str))
// Guid = str;
// DMI信息位于 /sys/class/dmi/id/ 目录可以直接读取不需要执行dmidecode命令
var uuid = string.Empty;
var file = "/sys/class/dmi/id/product_uuid";
if (!File.Exists(file)) file = "/etc/uuid";
if (!File.Exists(file)) file = "/proc/serial_num"; // miui12支持/proc/serial_num
if (TryRead(file, out value))
uuid = value;
else if (device.TryGetValue("Serial", out str) && str != "unknown")
uuid = str;
if (!uuid.IsNullOrEmpty()) UUID = uuid;
// 从release文件读取产品
var prd = GetProductByRelease();
if (!prd.IsNullOrEmpty()) Product = prd;
if (prd.IsNullOrEmpty() && TryRead("/sys/class/dmi/id/product_name", out var product_name))
{
Product = product_name;
// 增加制造商。如 Tencent Cloud它的产品名只有 CVM。阿里云产品名 Alibaba Cloud ECS
if (TryRead("/sys/class/dmi/id/sys_vendor", out var vendor) && !vendor.IsNullOrEmpty())
{
Vendor = vendor;
if (!product_name.IsNullOrEmpty() && !product_name.Contains(vendor))
{
// 红帽KVM太流行细化处理
if (product_name == "KVM" && vendor == "Red Hat" &&
TryRead("/sys/class/dmi/id/product_version", out var ver) && !ver.IsNullOrEmpty())
{
var p = ver.IndexOf('(');
if (p > 0) ver = ver[..p].Trim();
Product = ver;
}
}
}
}
file = "/sys/class/dmi/id/product_serial";
if (TryRead(file, out value)) Serial = value;
// 在DMI信息内没有太好的BoardID取值
file = "/sys/class/dmi/id/product_sku";
if (TryRead(file, out value) && !value.IsNullOrEmpty())
Board = value;
else
{
file = "/sys/class/dmi/id/product_family";
if (TryRead(file, out value)) Board = value;
}
var disks = GetFiles("/dev/disk/by-id", true);
if (disks.Count == 0) disks = GetFiles("/dev/disk/by-uuid", false);
if (disks.Count > 0) DiskID = disks.Where(e => !e.IsNullOrEmpty()).Join(",");
// 从*-release文件读取产品信息具有更高优先级
file = "/etc/os-release";
if (TryRead(file, out value))
{
var dic2 = value.SplitAsDictionary("=", Environment.NewLine, true);
if (dic2.TryGetValue("Vendor", out str)) Vendor = str;
if (dic2.TryGetValue("Product", out str)) Product = str;
if (dic2.TryGetValue("Serial", out str)) Serial = str;
if (dic2.TryGetValue("Board", out str)) Board = str;
}
}
private void LoadMacInfo()
{
var dic = ReadCommand("sw_vers");
if (dic != null)
{
if (dic.TryGetValue("ProductName", out var str)) OSName = str;
if (dic.TryGetValue("productVersion", out str)) OSVersion = str;
}
dic = ReadCommand("system_profiler", "SPHardwareDataType");
if (dic != null)
{
//if (dic2.TryGetValue("Model Name", out str)) Product = str;
if (dic.TryGetValue("Model Identifier", out var str)) Product = str;
if (dic.TryGetValue("Processor Name", out str)) Processor = str;
if (dic.TryGetValue("Memory", out str)) Memory = (UInt64)str.TrimEnd("GB").Trim().ToLong() * 1024 * 1024;
if (dic.TryGetValue("Serial Number (system)", out str)) Serial = str;
if (dic.TryGetValue("Hardware UUID", out str)) UUID = str;
if (dic.TryGetValue("Processor Name", out str)) Processor = str;
}
if (Vendor.IsNullOrEmpty()) Vendor = "Apple";
dic = ReadCommand("diskutil", "info disk1");
if (dic != null)
{
if (dic.TryGetValue("Disk / Partition UUID", out var str)) DiskID = str;
}
}
private readonly ICollection<String> _excludes = [];
/// <summary>获取实时数据如CPU、内存、温度</summary>
public void Refresh()
{
if (Runtime.Windows)
RefreshWindows();
// 特别识别Linux发行版
else if (Runtime.Linux)
RefreshLinux();
// 刷新 GC 与进程内存信息
RefreshMemoryInfo();
RefreshSpeed();
Provider?.Refresh(this);
}
/// <summary>
/// 刷新 GC 与进程内存相关信息
/// </summary>
private void RefreshMemoryInfo()
{
#if NET6_0_OR_GREATER
var info = GC.GetGCMemoryInfo();
var proc = Process.GetCurrentProcess();
// GC 信息单位MB
HighMemoryLoadThreshold = (ulong)(info.HighMemoryLoadThresholdBytes / 1024 / 1024);
TotalAvailableMemory = (ulong)(info.TotalAvailableMemoryBytes / 1024 / 1024);
HeapSize = (ulong)(info.HeapSizeBytes / 1024 / 1024);
TotalMemory = (ulong)(GC.GetTotalMemory(false) / 1024 / 1024);
FragmentedBytes = (ulong)(info.FragmentedBytes / 1024 / 1024);
GCAvailableMemory = (ulong)(info.TotalAvailableMemoryBytes - info.MemoryLoadBytes) / 1024 / 1024;
CommittedBytes = (ulong)(info.TotalCommittedBytes / 1024 / 1024);
TotalAllocatedBytes = (ulong)(GC.GetTotalAllocatedBytes(false) / 1024 / 1024);
#if NET8_0_OR_GREATER
TotalPauseDurationMs = (ulong)GC.GetTotalPauseDuration().TotalMilliseconds;
#endif
GcGen0Count = GC.CollectionCount(0);
GcGen1Count = GC.CollectionCount(1);
GcGen2Count = GC.CollectionCount(2);
IsServerGC = System.Runtime.GCSettings.IsServerGC;
GCLatencyMode = System.Runtime.GCSettings.LatencyMode;
PinnedObjectsCount = info.PinnedObjectsCount;
FinalizationPendingCount = info.FinalizationPendingCount;
// 进程信息单位MB
VirtualMemory = (ulong)(proc.VirtualMemorySize64 / 1024 / 1024);
PrivateMemory = (ulong)(proc.PrivateMemorySize64 / 1024 / 1024);
PeakWorkingSet = (ulong)(proc.PeakWorkingSet64 / 1024 / 1024);
WorkingSet = (ulong)(proc.WorkingSet64 / 1024 / 1024);
#endif
}
private void RefreshWindows()
{
MEMORYSTATUSEX ms = default;
ms.Init();
if (GlobalMemoryStatusEx(ref ms))
{
Memory = ms.ullTotalPhys / 1024 / 1024;
AvailableMemory = ms.ullAvailPhys / 1024 / 1024;
}
GetSystemTimes(out var idleTime, out var kernelTime, out var userTime);
var current = new SystemTime
{
IdleTime = idleTime.ToLong(),
TotalTime = kernelTime.ToLong() + userTime.ToLong(),
};
var idle = current.IdleTime - (_systemTime?.IdleTime ?? 0);
var total = current.TotalTime - (_systemTime?.TotalTime ?? 0);
_systemTime = current;
CpuRate = total == 0 ? 0 : Math.Round((Double)(total - idle) / total, 4);
var power = new PowerStatus();
#if NETFRAMEWORK
if (!_excludes.Contains(nameof(Temperature)))
{
// 读取主板温度不太准。标准方案是ring0通过IOPort读取CPU温度太难在基础类库实现
var str = GetInfo("Win32_TemperatureProbe", "CurrentReading");
if (!str.IsNullOrEmpty())
{
Temperature = str.SplitAsInt().Average();
}
else
{
str = GetInfo("MSAcpi_ThermalZoneTemperature", "CurrentTemperature", "root/wmi");
if (!str.IsNullOrEmpty())
Temperature = (str.SplitAsInt().Average() - 2732) / 10.0;
else
{
if (XTrace.Log.Level <= LogLevel.Debug) XTrace.WriteLine("Temperature信息无法读取");
_excludes.Add(nameof(Temperature));
Temperature = 0;
}
}
}
if (power.BatteryLifePercent > 0)
Battery = power.BatteryLifePercent;
else if (!_excludes.Contains(nameof(Battery)))
{
// 电池剩余
var str = GetInfo("Win32_Battery", "EstimatedChargeRemaining");
if (!str.IsNullOrEmpty())
Battery = str.SplitAsInt().Average() / 100.0;
else
{
if (XTrace.Log.Level <= LogLevel.Debug) XTrace.WriteLine("Battery信息无法读取");
_excludes.Add(nameof(Battery));
Battery = 0;
}
}
#else
if (!_excludes.Contains(nameof(Temperature)))
{
var temp = ReadWmic(@"/namespace:\\root\wmi path MSAcpi_ThermalZoneTemperature", "CurrentTemperature");
if (temp?.Count > 0)
{
if (temp.TryGetValue("CurrentTemperature", out var str) && !str.IsNullOrEmpty())
Temperature = (str.SplitAsInt().Average() - 2732) / 10.0;
}
else
{
if (XTrace.Log.Level <= LogLevel.Debug) XTrace.WriteLine("Temperature信息无法读取");
_excludes.Add(nameof(Temperature));
Temperature = 0;
}
}
if (power.BatteryLifePercent > 0)
Battery = power.BatteryLifePercent;
else if (!_excludes.Contains(nameof(Battery)))
{
var battery = ReadWmic("path win32_battery", "EstimatedChargeRemaining");
if (battery?.Count > 0)
{
if (battery.TryGetValue("EstimatedChargeRemaining", out var str) && !str.IsNullOrEmpty())
Battery = str.SplitAsInt().Average() / 100.0;
}
else
{
if (XTrace.Log.Level <= LogLevel.Debug) XTrace.WriteLine("Battery信息无法读取");
_excludes.Add(nameof(Battery));
Battery = 0;
}
}
#endif
}
/// <summary>
/// 🐳 容器内存使用
/// </summary>
/// <returns></returns>
private static (ulong Total, ulong Used) GetCGroupMemoryUsage()
{
try
{
string[] limitPaths = {
"/sys/fs/cgroup/memory/memory.limit_in_bytes", // cgroup v1
"/sys/fs/cgroup/memory.max" // cgroup v2
};
string[] usagePaths = {
"/sys/fs/cgroup/memory/memory.usage_in_bytes", // cgroup v1
"/sys/fs/cgroup/memory.current" // cgroup v2
};
ulong total = ReadFirstAvailable(limitPaths);
ulong used = ReadFirstAvailable(usagePaths);
// 容器无内存限制时 total 通常是 2^63-1忽略
if (total > (1UL << 60)) total = 0;
return (total, used);
}
catch (Exception)
{
return (0, 0);
}
static ulong ReadFirstAvailable(string[] paths)
{
foreach (var path in paths)
{
if (File.Exists(path))
{
var content = File.ReadAllText(path).Trim();
if (content == "max") return ulong.MaxValue;
if (ulong.TryParse(content, out var value)) return value;
}
}
return 0;
}
}
private void RefreshLinux()
{
var (totalMemory, usedMemory) = GetCGroupMemoryUsage();
if (totalMemory > 0 && usedMemory > 0 && totalMemory < ulong.MaxValue / 2)
{
Memory = totalMemory / 1024 / 1024;
AvailableMemory = (totalMemory - usedMemory) / 1024 / 1024;
}
else
{
var dic = ReadInfo("/proc/meminfo");
if (dic != null)
{
if (dic.TryGetValue("MemTotal", out var str) && !str.IsNullOrEmpty())
Memory = (UInt64)str.TrimEnd(" kB").ToLong();
ulong ma = 0;
if (dic.TryGetValue("MemAvailable", out str) && !str.IsNullOrEmpty())
{
ma = (UInt64)(str.TrimEnd(" kB").ToLong() / 1024);
}
//低于3.14内核的版本用 free+cache
var mf = (UInt64)(dic["MemFree"]?.TrimEnd(" kB").ToLong() / 1024 ?? 0);
var mc = (UInt64)(dic["Cached"]?.TrimEnd(" kB").ToLong() / 1024 ?? 0);
var bf = (UInt64)(dic["Buffers"]?.TrimEnd(" kB").ToLong() / 1024 ?? 0);
var free = mf + mc + bf;
AvailableMemory = ma > free ? ma : free;
}
}
// A2/A4温度获取BuildrootCPU温度和主板温度
if (TryRead("/sys/class/thermal/thermal_zone0/temp", out var value) ||
TryRead("/sys/class/thermal/thermal_zone1/temp", out value))
{
Temperature = value.ToDouble();
// 有时候温度会超过1000可能是毫度。机器温度不会低于0度
if (Temperature > 1000) Temperature /= 1000;
}
// respberrypi + fedora
else if (TryRead("/sys/class/thermal/thermal_zone0/temp", out value) ||
TryRead("/sys/class/hwmon/hwmon0/temp1_input", out value) ||
TryRead("/sys/class/hwmon/hwmon0/temp2_input", out value) ||
TryRead("/sys/class/hwmon/hwmon0/device/hwmon/hwmon0/temp2_input", out value) ||
TryRead("/sys/devices/virtual/thermal/thermal_zone0/temp", out value))
{
Temperature = value.ToDouble() / 1000;
}
// A2温度获取Ubuntu 16.04 LTS Linux 3.4.39
else if (TryRead("/sys/class/hwmon/hwmon0/device/temp_value", out value))
{
if (!value.IsNullOrEmpty()) Temperature = value.Substring(null, ":").ToDouble();
}
// 电池剩余
if (TryRead("/sys/class/power_supply/BAT0/energy_now", out var energy_now) &&
TryRead("/sys/class/power_supply/BAT0/energy_full", out var energy_full))
{
Battery = energy_now.ToDouble() / energy_full.ToDouble();
}
else if (TryRead("/sys/class/power_supply/battery/capacity", out var capacity))
{
Battery = capacity.ToDouble() / 100.0;
}
else if (Runtime.Mono)
{
var battery = ReadDeviceBattery();
if (battery.TryGetValue("ChargeLevel", out var obj)) Battery = obj.ToDouble();
}
var file = "/proc/stat";
if (!_excludes.Contains(nameof(CpuRate)) && File.Exists(file))
{
// CPU指标usernice, system, idle, iowait, irq, softirq
// cpu 57057 0 14420 1554816 0 443 0 0 0 0
try
{
using var reader = new StreamReader(file);
var line = reader.ReadLine();
if (!line.IsNullOrEmpty() && line.StartsWith("cpu"))
{
var vs = line.TrimStart("cpu").Trim().Split(' ');
var current = new SystemTime
{
IdleTime = vs[3].ToLong(),
TotalTime = vs.Take(7).Select(e => e.ToLong()).Sum().ToLong(),
};
var idle = current.IdleTime - (_systemTime?.IdleTime ?? 0);
var total = current.TotalTime - (_systemTime?.TotalTime ?? 0);
_systemTime = current;
CpuRate = total == 0 ? 0 : Math.Round((Double)(total - idle) / total, 4);
}
}
catch
{
_excludes.Add(nameof(_excludes));
}
}
}
private Int64 _lastTime;
private Int64 _lastSent;
private Int64 _lastReceived;
/// <summary>刷新网络速度</summary>
public void RefreshSpeed()
{
var sent = 0L;
var received = 0L;
try
{
// 包含本地环回和隧道网卡
// WSL获取网络列表时可能报错
foreach (var ni in NetworkInterface.GetAllNetworkInterfaces())
{
try
{
var st = ni.GetIPStatistics();
sent += st.BytesSent;
received += st.BytesReceived;
}
catch { }
}
}
catch { }
var now = Runtime.TickCount64;
if (_lastTime > 0)
{
var interval = now - _lastTime;
if (interval > 0)
{
var s1 = (sent - _lastSent) * 1000 / interval;
var s2 = (received - _lastReceived) * 1000 / interval;
if (s1 >= 0) UplinkSpeed = (UInt64)s1;
if (s2 >= 0) DownlinkSpeed = (UInt64)s2;
}
}
_lastSent = sent;
_lastReceived = received;
_lastTime = now;
}
#endregion
#region
/// <summary>获取Linux发行版名称</summary>
/// <returns></returns>
public static String? GetLinuxName()
{
var fr = "/etc/redhat-release";
if (TryRead(fr, out var value)) return value;
var dr = "/etc/debian-release";
if (TryRead(dr, out value)) return value;
var sr = "/etc/os-release";
if (TryRead(sr, out value)) return value?.SplitAsDictionary("=", "\n", true)["PRETTY_NAME"].Trim();
var uname = "uname".Execute("-sr", 0, false)?.Trim();
if (!uname.IsNullOrEmpty())
{
// 支持Android系统名
var ss = uname.Split('-');
foreach (var item in ss)
{
if (!item.IsNullOrEmpty() && item.StartsWithIgnoreCase("Android")) return item;
}
return uname;
}
return null;
}
private static String? GetProductByRelease()
{
var di = "/etc/".AsDirectory();
if (!di.Exists) return null;
foreach (var fi in di.GetFiles("*-release"))
{
if (!fi.Name.EqualIgnoreCase("redhat-release", "debian-release", "os-release", "system-release"))
{
var dic = File.ReadAllText(fi.FullName).SplitAsDictionary("=", "\n", true);
if (dic.TryGetValue("BOARD", out var str)) return str;
if (dic.TryGetValue("BOARD_NAME", out str)) return str;
}
}
return null;
}
private static Boolean TryRead(String fileName, [NotNullWhen(true)] out String? value)
{
value = null;
if (!File.Exists(fileName)) return false;
try
{
value = File.ReadAllText(fileName)?.Trim();
if (value.IsNullOrEmpty()) return false;
}
catch { return false; }
return true;
}
/// <summary>读取文件信息,分割为字典</summary>
/// <param name="file"></param>
/// <param name="separate"></param>
/// <returns></returns>
public static IDictionary<String, String?>? ReadInfo(String file, Char separate = ':')
{
if (file.IsNullOrEmpty() || !File.Exists(file)) return null;
var dic = new NullableDictionary<String, String?>(StringComparer.OrdinalIgnoreCase);
using var reader = new StreamReader(file);
while (!reader.EndOfStream)
{
// 按行读取
var line = reader.ReadLine();
if (line != null)
{
// 分割
var p = line.IndexOf(separate);
if (p > 0)
{
var key = line[..p].Trim();
var value = line[(p + 1)..].Trim();
dic[key] = value.TrimInvisible();
}
}
}
return dic;
}
private static IDictionary<String, String>? ReadCommand(String cmd, String? arguments = null)
{
var str = cmd.Execute(arguments, 0, false);
if (str.IsNullOrEmpty()) return null;
return str.SplitAsDictionary(":", "\n", true);
}
/// <summary>
/// 通过 PowerShell 命令读取信息
/// </summary>
public static IDictionary<String, String> ReadPowerShell(String command)
{
var dic = new Dictionary<String, String>(StringComparer.OrdinalIgnoreCase);
var args = $"-Command \"{command}\"";
var str = "powershell.exe".Execute(args, 3_000) ?? String.Empty;
if (!String.IsNullOrWhiteSpace(str))
{
foreach (var item in str.DecodeJson()!)
{
dic[item.Key] = item.Value?.ToString() ?? String.Empty;
}
}
return dic;
}
/// <summary>通过WMIC命令读取信息</summary>
/// <param name="type"></param>
/// <param name="keys"></param>
/// <returns></returns>
public static IDictionary<String, String> ReadWmic(String type, params String[] keys)
{
var dic = new Dictionary<String, IList<String>>(StringComparer.OrdinalIgnoreCase);
var dic2 = new Dictionary<String, String>(StringComparer.OrdinalIgnoreCase);
var args = $"{type} get {keys.Join(",")} /format:list";
var str = "wmic".Execute(args, 0, false)?.Trim();
if (str.IsNullOrEmpty()) return dic2;
var ss = str.Split("\r\n");
foreach (var item in ss)
{
var ks = item?.Split('=');
if (ks?.Length >= 2)
{
var k = ks[0].Trim();
var v = ks[1].Trim().TrimInvisible();
if (!k.IsNullOrEmpty() && !v.IsNullOrEmpty())
{
if (!dic.TryGetValue(k, out var list))
dic[k] = list = [];
list.Add(v);
}
}
}
// 排序,避免多个磁盘序列号时,顺序变动
foreach (var item in dic)
{
dic2[item.Key] = item.Value.OrderBy(e => e).Join();
}
return dic2;
}
/// <summary>
/// 获取设备信息。用于Xamarin
/// </summary>
/// <returns></returns>
public static IDictionary<String, String?> ReadDeviceInfo()
{
var dic = new Dictionary<String, String?>();
if (!Runtime.Mono) return dic;
{
var type = "Android.OS.Build".GetTypeEx();
if (type != null)
{
foreach (var item in type.GetProperties(BindingFlags.Public | BindingFlags.Static))
{
try
{
dic[item.Name] = item.GetValue(null) + string.Empty;
}
catch { }
}
}
}
{
var type = "Xamarin.Essentials.DeviceInfo".GetTypeEx();
if (type != null)
{
foreach (var item in type.GetProperties(BindingFlags.Public | BindingFlags.Static))
{
try
{
dic[item.Name] = item.GetValue(null) + string.Empty;
}
catch { }
}
}
}
{
var type = "Android.Provider.Settings".GetTypeEx()?.GetNestedType("Secure");
if (type != null)
{
var resolver = "Android.App.Application".GetTypeEx()?.GetValue("Context")?.GetValue("ContentResolver");
if (resolver != null)
{
var name = "android_id";
dic[name] = type.Invoke("GetString", resolver, name) as String;
}
}
}
return dic;
}
/// <summary>
/// 获取设备电量。用于 Xamarin
/// </summary>
/// <returns></returns>
public static IDictionary<String, Object?> ReadDeviceBattery()
{
var dic = new Dictionary<String, Object?>();
if (!Runtime.Mono) return dic;
var type = "Xamarin.Essentials.Battery".GetTypeEx();
if (type == null) return dic;
foreach (var item in type.GetProperties(BindingFlags.Public | BindingFlags.Static))
{
try
{
dic[item.Name] = item.GetValue(null);
}
catch { }
}
return dic;
}
#endregion
#region
[DllImport("Kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[SecurityCritical]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern Boolean GlobalMemoryStatusEx(ref MEMORYSTATUSEX lpBuffer);
internal struct MEMORYSTATUSEX
{
internal UInt32 dwLength;
internal UInt32 dwMemoryLoad;
internal UInt64 ullTotalPhys;
internal UInt64 ullAvailPhys;
internal UInt64 ullTotalPageFile;
internal UInt64 ullAvailPageFile;
internal UInt64 ullTotalVirtual;
internal UInt64 ullAvailVirtual;
internal UInt64 ullAvailExtendedVirtual;
internal void Init() => dwLength = checked((UInt32)Marshal.SizeOf(typeof(MEMORYSTATUSEX)));
}
#endregion
#region
/// <summary>获取指定目录所在盘可用空间,默认当前目录</summary>
/// <param name="path"></param>
/// <returns>返回可用空间,字节,获取失败返回-1</returns>
public static Int64 GetFreeSpace(String? path = null)
{
if (path.IsNullOrEmpty()) path = ".";
var root = Path.GetPathRoot(path.GetFullPath());
if (root.IsNullOrEmpty()) return 0;
var driveInfo = new DriveInfo(root);
if (driveInfo?.IsReady != true) return -1;
try
{
return driveInfo.AvailableFreeSpace;
}
catch
{
return -1;
}
}
/// <summary>获取指定目录下文件名支持去掉后缀的去重主要用于Linux</summary>
/// <param name="path"></param>
/// <param name="trimSuffix"></param>
/// <returns></returns>
public static ICollection<String> GetFiles(String path, Boolean trimSuffix = false)
{
var list = new List<String>();
if (path.IsNullOrEmpty()) return list;
var di = path.AsDirectory();
if (!di.Exists) return list;
var list2 = di.GetFiles().Select(e => e.Name).ToList();
foreach (var item in list2)
{
var line = item?.Trim();
if (!line.IsNullOrEmpty())
{
if (trimSuffix)
{
if (!list2.Any(e => e != line && line.StartsWith(e))) list.Add(line);
}
else
{
list.Add(line);
}
}
}
return list;
}
#endregion
#region Windows辅助
[DllImport("kernel32.dll", SetLastError = true)]
private static extern Boolean GetSystemTimes(out FILETIME idleTime, out FILETIME kernelTime, out FILETIME userTime);
private struct FILETIME
{
public UInt32 Low;
public UInt32 High;
public FILETIME(Int64 time)
{
Low = (UInt32)time;
High = (UInt32)(time >> 32);
}
public Int64 ToLong() => (Int64)(((UInt64)High << 32) | Low);
}
private class SystemTime
{
public Int64 IdleTime;
public Int64 TotalTime;
}
private SystemTime? _systemTime;
#if NETFRAMEWORK
/// <summary>获取WMI信息</summary>
/// <param name="path"></param>
/// <param name="property"></param>
/// <param name="nameSpace"></param>
/// <returns></returns>
public static String GetInfo(String path, String property, String? nameSpace = null)
{
// Linux Mono不支持WMI
if (Runtime.Mono) return string.Empty;
var bbs = new List<String>();
try
{
var wql = $"Select {property} From {path}";
var cimobject = new ManagementObjectSearcher(nameSpace, wql);
var moc = cimobject.Get();
foreach (var mo in moc)
{
var val = mo?.Properties?[property]?.Value;
if (val != null)
{
var v = val.ToString().TrimInvisible()?.Trim();
if (v != null) bbs.Add(v);
}
}
}
catch (Exception ex)
{
if (XTrace.Log.Level <= LogLevel.Debug) XTrace.WriteLine("WMI.GetInfo({0})失败!{1}", path, ex.Message);
return string.Empty;
}
bbs.Sort();
return bbs.Distinct().Join();
}
#endif
#endregion
}