本文是这个系列的最后一篇文章,前几篇讲了系统性能数据来源的分类、常见数据源、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();
        }
* 最后一块西部数据的硬盘是一块IDE接口的老式硬盘,通电时间数据已经溢出了,获取到的数据与实际不符。

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");
        }
分类: articles