1399 lines
48 KiB
C#
1399 lines
48 KiB
C#
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_id,Android的android_id,Ghost系统存在重复</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温度获取,Buildroot,CPU温度和主板温度
|
||
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指标:user,nice, 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
|
||
} |