本文是这个系列的最后一篇文章,前几篇讲了系统性能数据来源的分类、常见数据源、Windows和Linux系统下不同类型数据源的数据获取演示,本篇会对前几篇文章所介绍的性能数据作个补充,讲一下Windows和Linux里如何获取硬盘健康状态数据(S.M.A.R.T数据)。
Windows使用的数据源是aida64报告,会演示包括S.M.A.R.T数据在内的常见关键性能数据的读取,Linux下会演示如何借助smartctl命令获取硬盘健康数据,顺带介绍一下如何通过ssh在远程主机执行命令并获取结果。
大部分性能数据都存在不止一种数据来源,所以真正在使用过程中,数据源的选择并非只有唯一的答案,而是完全取决于方便程度和个人喜好。
1. Windows下,利用aida64报表收集数据
这种数据获取方式本质上是利用外部程序生成外部数据源,间接获取数据,与直接获取外部程序的标准输出有所区别,因此重点是大量的字符串处理操作。本例将获取CPU使用率、内存使用率、硬盘健康状态、分区使用情况这几项数据。
aida64 /R D:\result.txt/CUSTOM D:\xxx.rpf /TXT
# result.csv 指定生成的报告文件位置
# /CUSTOM file.rpf 自定义报告,通过D:\xxx.rpf指定字段
# /CSV生成csv格式的报表
# D:\xxx.rpf 文件内容
InfoPage="Motherboard;CPU"
InfoPage="Motherboard;Memory"
InfoPage="Storage;Logical Drives"
InfoPage="Storage;SMART"
static void Main(string[] args)
{
string filereport = @"C:\LAB\Report.txt";
double scale(string unit) => unit switch
{
"M" => 0.001,
"T" => 1000,
_ => 1
};
//Process proc =
// Process.Start(
// @"C:\LAB\aida64\aida64.exe",
// $@"/R {filereport} /CUSTOM C:\LAB\aida64\1.rpf /TXT");
//proc.WaitForExit();
string report = System.IO.File.ReadAllText(filereport, Encoding.GetEncoding("GBK"));
var match =
Regex.Match(report, @"日期\s+(\d{4}-\d{2}-\d{2})\s+时间\s+(\d{2}:\d{2})");
Console.WriteLine(
$"检测日期:{match.Groups[1].Value} {match.Groups[2].Value}\r\n");
//CPU
var matches = Regex.Matches(
report,
@"CPU\s+#(\d+)/核心\s+#(\d+)/SMT\s+#(\d+)\s+([0-9.]+)%",
RegexOptions.Singleline).
Cast<Match>().
Select(m =>
(m.Groups[1].Value,
m.Groups[2].Value,
m.Groups[3].Value,
m.Groups[4].Value + "%"));
int cpuCount = Convert.ToInt32(matches.Max(m => m.Item1));
int cpuCores = Convert.ToInt32(matches.Max(m => m.Item2));
int cpuThreads = Convert.ToInt32(matches.Max(m => m.Item3)) * cpuCores;
Console.WriteLine(
$"CPU数量 {cpuCount}, \t核数 {cpuCores}, \t线程数 {cpuThreads}\r\n" +
$"使用率: {string.Join("\t", matches.Select(m => m.Item4))}\r\n");
//内存
match = Regex.Match(
report,
@"物理内存:.*?总数\s+(\d+)\s+MB\s+已用\s+(\d+)\s+MB",
RegexOptions.Singleline);
int memTotal = Convert.ToInt32(match.Groups[1].Value);
int memUsed = Convert.ToInt32(match.Groups[2].Value);
Console.WriteLine(
$"内存:总量 {memTotal}M\t" +
$"空闲 {memTotal - memUsed}M\t" +
$"已用 {memUsed}({(1 - memUsed * 1.0 / memTotal).ToString("0.00%")})\t\n\r\n磁盘空间:");
//分区
string seg = Regex.Match(report, @"\[\s+逻辑驱动器\s+]-+\s.*?---", RegexOptions.Singleline).Value;
var disks = Regex.Matches(
seg,
@"([A-Z]:).*?(\d+\s[MGT]B)\s+(\d+\s[MGT]B)\s",
RegexOptions.Singleline).Cast<Match>().
Select(m =>
(m.Groups[1].Value,
Convert.ToInt32(Regex.Match(m.Groups[2].Value, @"\d+").Value),
Regex.Match(m.Groups[2].Value, "[MGT](?=B)").Value,
Convert.ToInt32(Regex.Match(m.Groups[3].Value, @"\d+").Value),
Regex.Match(m.Groups[3].Value, "[MGT](?=B)").Value
));// 盘符, 总容量, 单位, 已使用, 单位
foreach (var disk in disks)
{
double spaceTotal = disk.Item2 * scale(disk.Item3);
double spaceUsed = disk.Item4 * scale(disk.Item5);
Console.WriteLine(
$"{disk.Item1}\t" +
$"共 {spaceTotal.ToString("0.00")} GB\t" +
$"已用 {spaceUsed.ToString("0.00")} GB" +
$"({(spaceUsed / spaceTotal).ToString("0.00%")})");
}
Console.WriteLine("\r\n硬盘健康状态");
//S.M.A.R.T 09通电时间,0C次数,05重映射扇区,C7数据传输错误,C5待映射扇区,C2温度
seg = Regex.Match(report, @"(?<=\[\s+SMART\s+])-+\s.*?---", RegexOptions.Singleline).Value;
var smarts =
Regex.Matches(
seg,
@"\[(.*?)\].*?05\s+Reallocated.*?\d+\s+\d+\s+\d+\s+(\d+).*?" +
@"09\s+Power.*?\d+\s+\d+\s+\d+\s+(\d+).*?" +
@"0C\s+Power.*?\d+\s+\d+\s+\d+\s+(\d+).*?" +
@"C2\s+[a-zA-Z]+.*?\d+\s+\d+\s+\d+\s+(\d+).*?" +
@"C5\s+Current.*?\d+\s+\d+\s+\d+\s+(\d+).*?" +
@"C7\s+Ultra.*?\d+\s+\d+\s+\d+\s+(\d+).*?",
RegexOptions.Singleline);
foreach (Match m in smarts)
{
Console.WriteLine(
$"【{m.Groups[1].Value}】当前温度 {m.Groups[5].Value}\r\n" +
$"通电 {m.Groups[4].Value} 次,{m.Groups[3].Value} 小时\t" +
$"05(已映射) {m.Groups[2].Value}\tC5(待映射) {m.Groups[6].Value}\t" +
$"C7(传输错误) {m.Groups[7].Value}\r\n");
}
Console.ReadLine();
}
2. Linux下利用smartctl获取硬盘状态,以及ssh远程执行命令并获取结果
Linux系统毕竟是客场,所以.Net只能通过外部数据源或者外部软件的输出结果获取性能数据。本例将演示如何通过smartctl命令获取硬盘健康状态,同时也会演示如何通过ssh远程执行命令并获取结果(ssh到localhost执行 top -bn1),通过类似的操作,完全可以在一台服务器上获取其它服务器的性能数据、甚至执行指定任务。
static void Main(string[] args)
{
Directory.GetFiles("/dev", "sd?").ToList().ForEach(d => GetSmartInfo(d));
int k2m(Group g) => Convert.ToInt32(g.Value) >> 10;
//ssh
//SSH远程执行
ProcessStartInfo startInfo =
new ProcessStartInfo("ssh", "localhost \"top -bn1\"")
{
RedirectStandardOutput = true,
UseShellExecute = false
};
var result = Process.Start(startInfo).StandardOutput.ReadToEnd();
Match m = Regex.Match(
result,
@"ni,\s+([0-9.]+)\s+id,.*?KiB\s+Mem.*?([0-9.]+)\s+total,.*?([0-9.]+)\s+used,.*?KiB Swap.*?([0-9.]+)\s+total,.*?([0-9.]+)\s+used\.",
RegexOptions.Singleline);
Console.WriteLine("远程服务器状态(" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss)\r\n") +
$"CPU IDLE:\t{m.Groups[1]}%\r\n" +
$"Mem Used:\t{k2m(m.Groups[3])}M of {k2m(m.Groups[2])}M\r\n" +
$"SwapUsed:\t{k2m(m.Groups[5])}M of {k2m(m.Groups[4])}M\r\n");
}
static void GetSmartInfo(string disk)
{
ProcessStartInfo startInfo =
new ProcessStartInfo("/usr/sbin/smartctl", $"-AiH {disk}")
{
RedirectStandardOutput = true,
UseShellExecute = false
};
string result =
Process.Start(startInfo).StandardOutput.ReadToEnd();
bool isSSD = result.Contains("SSD");
var smarts =
Regex.Match(
result,
@"Device\s+Model:\s+(.*?)\s+Serial.*?test\s+result:\s+(\S+).*?" +
@"\s+5\s+Reallocated.*?\s+-\s+(\S+).*?" +
@"\s+9\s+Power.*?\s+-\s+(\S+).*?" +
@"\s12\s+Power.*?\s+-\s+(\S+).*?" +
@"194\s+Temperature.*?\s+-\s+(\S+).*?" +
(isSSD ?
@"232\s+Available.*?\s+-\s+(\S+).*?" :
@"197\s+Current.*?\s+-\s+(\S+).*?199\s+UDMA.*?\s+-\s+(\S+).*?"),
RegexOptions.Singleline);
//1型号,2检测结果,3(05重映射),4(09通电时长),5(12/0C通电次数),
//6(194/C2温度),7(197/C5待映射、232/E8剩余块),8(199/C7传输错误)
Console.WriteLine(
$"【{smarts.Groups[1].Value}】\t" +
$"健康测试 {smarts.Groups[2].Value}\t当前温度{smarts.Groups[6].Value}\r\n" +
$"通电 {smarts.Groups[5].Value} 次,{smarts.Groups[4].Value} 小时\t" +
$"05(已映射) {smarts.Groups[3].Value}\t" +
(isSSD ?
$"E8(剩余块) {smarts.Groups[7].Value}" :
$"C5(待映射) {smarts.Groups[7].Value}\tC7(传输错误) {smarts.Groups[8].Value}") + "\r\n");
}