Skip to content

pve安全温控

这里的pve版本为7.x

一、安装sensors

apt-get install lm-sensors
#探测下温度,执行:(一路yes,回车)
sensors-detect
#获取温度信息,执行:
sensors

image-20220722141449738

  • ACPI interface那里是主板温度:temp1和temp2 (有些主板不一样,建议不管主板温度)

  • ISA adapter那里是CPU温度:Core0和Core1 (几个核心就是显示几个,演示机只有双核,所以只有2个)

#如果是全新安装,需要安装工具并重启 pveproxy 服务
apt-get install lm-sensors && apt-get install nvme-cli && apt-get install hddtemp && chmod +s /usr/sbin/nvme && chmod +s /usr/sbin/hddtemp && chmod +s /usr/sbin/smartctl && systemctl restart pveproxy
a) 如果是全新安装或者首次更新并使用 PVE 7.2+,需要安装工具并重启 pveproxy 服务:
apt-get install -y lm-sensors && chmod +s /usr/sbin/smartctl && systemctl restart pveproxy
b) 如果需要显示磁盘 I/O 信息,需要安装工具并重启 pveproxy 服务:
apt-get install -y sysstat && systemctl restart pveproxy
c) 如果是更新脚本,只需要重启 pveproxy 服务:
systemctl restart pveproxy

1.1 修改web文件

在修改之前最好备份一下,防止出问题回退,下述的为。

1.2 修改/usr/share/perl5/PVE/API2/Nodes.pm

搜索

$res->{pveversion} = PVE::pvecfg::package()

在下方插入

$res->{thermalstate} = `sensors`;

如果是服务器12核的主板(E5-2678 v3)类似的:

    my $cpufreqs = `lscpu | grep MHz`;
    my $corefreqs = `cat /proc/cpuinfo | grep -i  "cpu MHz"`;
    $res->{cpu_frequency} = $cpufreqs . $corefreqs;

    $res->{cpu_temperatures} = `sensors`;

    my $nvme_ssd1_temperatures = `smartctl -a /dev/nvme0|grep -E "Model Number|Total NVM Capacity|Temperature:|Percentage|Data Unit|Power Cycles|Power On Hours|Unsafe Shutdowns"`;
    my $nvme_ssd1_io = `iostat -d -x -k 1 1 | grep -E 'nvme0'`;
    $res->{nvme_ssd1_status} = $nvme_ssd1_temperatures . $nvme_ssd1_io;

    my $nvme_ssd2_temperatures = `smartctl -a /dev/nvme1|grep -E "Model Number|Total NVM Capacity|Temperature:|Percentage|Data Unit|Power Cycles|Power On Hours|Unsafe Shutdowns"`;
    my $nvme_ssd2_io = `iostat -d -x -k 1 1 | grep -E 'nvme1'`;
    $res->{nvme_ssd2_status} = $nvme_ssd2_temperatures . $nvme_ssd2_io;

    my $hdd1_temperatures = `smartctl -a /dev/sda|grep -E "Model|Capacity|Power_On_Hours|Power_Cycle_Count|Unexpected_Power_Loss|Unexpect_Power_Loss_Ct|Temperature"`;
    my $hdd1_io = `iostat -d -x -k 1 1 | grep -E 'sda'`;
    $res->{hdd1_status} = $hdd1_temperatures . $hdd1_io;

    my $hdd2_temperatures = `smartctl -a /dev/sdb|grep -E "Model|Capacity|Power_On_Hours|Power_Cycle_Count|Unexpected_Power_Loss|Unexpect_Power_Loss_Ct|Temperature"`;
    my $hdd2_io = `iostat -d -x -k 1 1 | grep -E 'sdb'`;
    $res->{hdd2_status} = $hdd2_temperatures . $hdd2_io;

    my $hdd3_temperatures = `smartctl -a /dev/sdc|grep -E "Model|Capacity|Power_On_Hours|Power_Cycle_Count|Unexpected_Power_Loss|Unexpect_Power_Loss_Ct|Temperature"`;
    my $hdd3_io = `iostat -d -x -k 1 1 | grep -E 'sdc'`;
    $res->{hdd3_status} = $hdd3_temperatures . $hdd3_io;

    my $hdd4_temperatures = `smartctl -a /dev/sdd|grep -E "Model|Capacity|Power_On_Hours|Power_Cycle_Count|Unexpected_Power_Loss|Unexpect_Power_Loss_Ct|Temperature"`;
    my $hdd4_io = `iostat -d -x -k 1 1 | grep -E 'sdd'`;
    $res->{hdd4_status} = $hdd4_temperatures . $hdd4_io;

    my $hdd5_temperatures = `smartctl -a /dev/sde|grep -E "Model|Capacity|Power_On_Hours|Power_Cycle_Count|Unexpected_Power_Loss|Unexpect_Power_Loss_Ct|Temperature"`;
    my $hdd5_io = `iostat -d -x -k 1 1 | grep -E 'sde'`;
    $res->{hdd5_status} = $hdd5_temperatures . $hdd5_io;

image-20220722142729267

1.3 修改/usr/share/pve-manager/js/pvemanagerlib.js

搜索

PVE Manager Version

在这个json文件的节点后面添加:

这里是双核心:

    {
          itemId: 'thermal',
          colspan: 2,
          printBar: false,
          title: gettext('CPU温度'),
          textField: 'thermalstate',
          renderer:function(value){
              const p0 = value.match(/Package id 0.*?\+([\d\.]+)Â/)[1];
              const c0 = value.match(/Core 0.*?\+([\d\.]+)Â/)[1];
              const c1 = value.match(/Core 1.*?\+([\d\.]+)Â/)[1];
              const b0 = value.match(/temp1.*?\+([\d\.]+)?/)[1];
              const b1 = value.match(/temp2.*?\+([\d\.]+)?/)[1];
              return `Package: ${p0} ℃ || 核心1: ${c0} ℃ | 核心2: ${c1} ℃ || 主板: ${b0} ℃ | ${b1} ℃ `
            }
    },

这是四核心的:

    {
          itemId: 'thermal',
          colspan: 2,
          printBar: false,
          title: gettext('CPU温度'),
          textField: 'thermalstate',
          renderer:function(value){
              const p0 = value.match(/Package id 0.*?\+([\d\.]+)Â/)[1];
              const c0 = value.match(/Core 0.*?\+([\d\.]+)Â/)[1];
              const c1 = value.match(/Core 1.*?\+([\d\.]+)Â/)[1];
              const c2 = value.match(/Core 2.*?\+([\d\.]+)Â/)[1];
              const c3 = value.match(/Core 3.*?\+([\d\.]+)Â/)[1];
              return `Package: ${p0} ℃ || 核心1: ${c0} ℃ | 核心2: ${c1} ℃ | 核心3: ${c2} ℃ | 核心4: ${c3} ℃ `
            }
    },

12核心的

    {
        itemId: 'cpu-frequency',
        colspan: 2,
        printBar: false,
        title: gettext('CPU主频'),
        textField: 'cpu_frequency',
        renderer:function(value){
            let output = '';
            let cpufreqs = value.matchAll(/^CPU MHz.*?(\d+\.\d+)\n^CPU max MHz.*?(\d+)\.\d+\n^CPU min MHz.*?(\d+)\.\d+\n/gm);
              for (const cpufreq of cpufreqs) {
                  output += `实时: ${cpufreq[1]} MHz | 最低: ${cpufreq[3]} MHz | 最高: ${cpufreq[2]} MHz\n`;
              }

            let corefreqs = value.match(/^cpu MHz.*?(\d+\.\d+)/gm);
                if (corefreqs.length > 0) {
                      for (i = 1;i < corefreqs.length;) {
                          for (const corefreq of corefreqs) {
                              output += `线程 ${i++}: ${corefreq.match(/(?<=:\s+)(\d+\.\d+)/g)} MHz`;
                              output += ' | ';
                              if ((i-1) % 4 == 0){
                                  output = output.slice(0, -2);
                                  output += '\n';
                              }
                          }
                      }
                } else {
                    output += '(';
                    output += `${corefreqs}`;
                    output += ')';
                }
            return output.replace(/\n/g, '<br>');
        }
    },
    {
        itemId: 'cpu-temperatures',
        colspan: 2,
        printBar: false,
        title: gettext('CPU温度'),
        textField: 'cpu_temperatures',
        renderer: function(value) {
            value = value.replace(/Â/g, '');
            let data = [];
            let cpus = value.matchAll(/^coretemp-isa-(\d{4})$\n.*?\n((?:Package|Core)[\s\S]*?^\n)+/gm);
            for (const cpu of cpus) {
                let cpuNumber = parseInt(cpu[1], 10);
                data[cpuNumber] = {
                       packages: [],
                       cores: []
                };

                let packages = cpu[2].matchAll(/^Package id \d+:\s*\+([^°]+).*$/gm);
                for (const package of packages) {
                    data[cpuNumber]['packages'].push(package[1]);
                }

                let cores = cpu[2].matchAll(/^Core \d+:\s*\+([^°]+).*$/gm);
                for (const core of cores) {
                    data[cpuNumber]['cores'].push(core[1]);
                }
            }

            let output = '';
            for (const [i, cpu] of data.entries()) {
                if (cpu.packages.length > 0) {
                    for (const packageTemp of cpu.packages) {
                        output += `CPU ${i+1}: ${packageTemp}°C `;
                    }
                }

                if (cpu.cores.length > 0) {
                    output += '(';
                    for (j = 1;j < cpu.cores.length;) {
                        for (const coreTemp of cpu.cores) {
                            output += `核心 ${j++}: ${coreTemp}°C, `;
                        }
                    }
                    output = output.slice(0, -2);
                    output += ')';
                }
            }

            let acpitzs = value.matchAll(/acpitz-acpi-(\d*)\n.*?\n((?:temp)[\s\S]*?\n)+/gm);
            for (const acpitz of acpitzs) {
                let acpitzNumber = parseInt(acpitz[1], 10);
                data[acpitzNumber] = {
                       acpisensors: []
                };

                let acpisensors = acpitz[2].matchAll(/^temp\d+:\s*\+([^°]+).*$/gm);
                for (const acpisensor of acpisensors) {
                    data[acpitzNumber]['acpisensors'].push(acpisensor[1]);
                }

                for (const [k, acpitz] of data.entries()) {
                      if (acpitz.acpisensors.length > 0) {
                        output += ' | 主板: ';
                        for (m = 1; m <= acpitz.acpisensors.length; m++) {
                            for (const acpiTemp of acpitz.acpisensors) {
                                output += `${acpiTemp}°C, `;
                            }
                        }
                        output = output.slice(0, -2);
                    }
                }
            }

            return output;
        }
    },
    {
        itemId: 'nvme_ssd1-status',
        colspan: 2,
        printBar: false,
        title: gettext('NVME硬盘 1'),
        textField: 'nvme_ssd1_status',
        renderer:function(value){
            if (value.length > 0) {
                let nvmedevices = value.matchAll(/^Model.*:\s*([\s\S]*?)\n(^Total.*\[[\s\S]*?\]){0,1}\n{0,1}^Temperature:\s*(\d+)\s*Celsius\n^Percentage.*(\d+\%)\n^Data Units.*\[([\s\S]*?)\]\n^Data Units.*\[([\s\S]*?)\]\n^Power Cycles:\s*([\s\S]*?)\n^Power On Hours:\s*([\s\S]*?)\n^Unsafe Shutdowns.*:\s*([\s\S]*?)\nnvme\S+((\s*?\d+\.\d{2}){22})/gm);
                for (const nvmedevice of nvmedevices) {
                    nvmemodel = nvmedevice[1].replace(/^Model.*:\s*([\s\S]*?)/m, '$1');

                    if (value.indexOf("Capacity") !== -1){
                        nvmecapacity = nvmedevice[2].replace(/.*\[([\s\S]*?)\]/m, '$1');
                        nvmecapacity = nvmecapacity.replace(/ /, '');
                        nvmecapacity = ` | 容量: ${nvmecapacity}`;
                    } else {
                          nvmecapacity = ``;
                    }

                    nvmepowerhour = nvmedevice[8].replace(/ |,/gm, '');

                  nvme_status = nvmedevice[10].replace(/\s+/gm, ',');
                  nvme_status = nvme_status.replace(/^,/gm, '');
                  nvme_status = nvme_status.split(",");
                  nvme_r_await = nvme_status[4]
                  nvme_w_await = nvme_status[10]
                  nvme_util = nvme_status[21]

                    if(value.indexOf("Cycles") !== -1){
                          nvmepowercycle = `${nvmedevice[7]}次`;

                          let nvmepowerloss = '';
                          if(value.indexOf("Unsafe") !== -1){
                              nvmepowerloss = `, 不安全断电${nvmedevice[9]}次`;
                          }

                          nvmepowercount = `${nvmepowercycle}${nvmepowerloss}`;

                          value = `${nvmemodel}${nvmecapacity} | 寿命: ${nvmedevice[4]} (读取: ${nvmedevice[5].replace(/ /, '')}, 写入: ${nvmedevice[6].replace(/ /, '')}) | 温度: ${nvmedevice[3]}°C\n通电: ${nvmepowercount}, 累计${nvmepowerhour}小时 | I/O: 读延迟${nvme_r_await}ms, 写延迟${nvme_w_await}ms, 负载${nvme_util}%\n`;
                    } else {
                          value = `${nvmemodel}${nvmecapacity} | 寿命: ${nvmedevice[4]} (读取: ${nvmedevice[5].replace(/ /, '')}, 写入: ${nvmedevice[6].replace(/ /, '')}) | 通电: ${nvmepowerhour}小时 | I/O: 读延迟${nvme_r_await}ms, 写延迟${nvme_w_await}ms, 负载${nvme_util}% | 温度: ${nvmedevice[31]}°C\n`;
                    }
                }
                return value.replace(/\n/g, '<br>');
            } else {
                return `提示: 未安装硬盘或已直通硬盘控制器`;
            }
        }
    },
    {
        itemId: 'nvme_ssd2-status',
        colspan: 2,
        printBar: false,
        title: gettext('NVME硬盘 2'),
        textField: 'nvme_ssd2_status',
        renderer:function(value){
            if (value.length > 0) {
                let nvmedevices = value.matchAll(/^Model.*:\s*([\s\S]*?)\n(^Total.*\[[\s\S]*?\]){0,1}\n{0,1}^Temperature:\s*(\d+)\s*Celsius\n^Percentage.*(\d+\%)\n^Data Units.*\[([\s\S]*?)\]\n^Data Units.*\[([\s\S]*?)\]\n^Power Cycles:\s*([\s\S]*?)\n^Power On Hours:\s*([\s\S]*?)\n^Unsafe Shutdowns.*:\s*([\s\S]*?)\nnvme\S+((\s*?\d+\.\d{2}){22})/gm);
                for (const nvmedevice of nvmedevices) {
                    nvmemodel = nvmedevice[1].replace(/^Model.*:\s*([\s\S]*?)/m, '$1');

                    if (value.indexOf("Capacity") !== -1){
                        nvmecapacity = nvmedevice[2].replace(/.*\[([\s\S]*?)\]/m, '$1');
                        nvmecapacity = nvmecapacity.replace(/ /, '');
                        nvmecapacity = ` | 容量: ${nvmecapacity}`;
                    } else {
                          nvmecapacity = ``;
                    }

                    nvmepowerhour = nvmedevice[8].replace(/ |,/gm, '');

                  nvme_status = nvmedevice[10].replace(/\s+/gm, ',');
                  nvme_status = nvme_status.replace(/^,/gm, '');
                  nvme_status = nvme_status.split(",");
                  nvme_r_await = nvme_status[4]
                  nvme_w_await = nvme_status[10]
                  nvme_util = nvme_status[21]

                    if(value.indexOf("Cycles") !== -1){
                          nvmepowercycle = `${nvmedevice[7]}次`;

                          let nvmepowerloss = '';
                          if(value.indexOf("Unsafe") !== -1){
                              nvmepowerloss = `, 不安全断电${nvmedevice[9]}次`;
                          }

                          nvmepowercount = `${nvmepowercycle}${nvmepowerloss}`;

                          value = `${nvmemodel}${nvmecapacity} | 寿命: ${nvmedevice[4]} (读取: ${nvmedevice[5].replace(/ /, '')}, 写入: ${nvmedevice[6].replace(/ /, '')}) | 温度: ${nvmedevice[3]}°C\n通电: ${nvmepowercount}, 累计${nvmepowerhour}小时 | I/O: 读延迟${nvme_r_await}ms, 写延迟${nvme_w_await}ms, 负载${nvme_util}%\n`;
                    } else {
                          value = `${nvmemodel}${nvmecapacity} | 寿命: ${nvmedevice[4]} (读取: ${nvmedevice[5].replace(/ /, '')}, 写入: ${nvmedevice[6].replace(/ /, '')}) | 通电: ${nvmepowerhour}小时 | I/O: 读延迟${nvme_r_await}ms, 写延迟${nvme_w_await}ms, 负载${nvme_util}% | 温度: ${nvmedevice[31]}°C\n`;
                    }
                }
                return value.replace(/\n/g, '<br>');
            } else {
                return `提示: 未安装硬盘或已直通硬盘控制器`;
            }
        }
    },
    {
        itemId: 'hdd1-status',
        colspan: 2,
        printBar: false,
        title: gettext('SATA硬盘 1'),
        textField: 'hdd1_status',
        renderer:function(value){
            if (value.length > 0) {
                let devices = value.matchAll(/(\s*Model.*:\s*[\s\S]*?\n){1,2}^User.*\[([\s\S]*?)\]\n^\s*9[\s\S]*?\-\s*(\d+)[\s\S]*?\n(^\s*12[\s\S]*?\-\s*\d+[\s\S]*?\n){0,1}(^174[\s\S]*?\-\s*\d+[\s\S]*?\n){0,1}(^19[0,4][\s\S]*?$){0,2}\nsd\S+((\s*?\d+\.\d{2}){22})/gm);
                for (const device of devices) {
                    if(device[1].indexOf("Family") !== -1){
                          devicemodel = device[1].replace(/.*Model Family:\s*([\s\S]*?)\n^Device Model:\s*([\s\S]*?)\n/m, '$1 - $2');
                    } else {
                          devicemodel = device[1].replace(/.*Model:\s*([\s\S]*?)\n/m, '$1');
                    }

                    devicecapacity = device[2].replace(/ |,/gm, '');

                    if (value.indexOf("Temperature") !== -1){
                          devicetemp = device[6].replace(/19[0,4].*[\-|In_the_past]\s*(\d+).*/m, '温度: $1°C');
                    } else {
                          devicetemp = `提示: 未检测到温度传感器`;
                    }

                    devicepowerhour = device[3].replace(/ |,/gm, '');

                  device_status = device[7].replace(/\s+/gm, ',');
                  device_status = device_status.replace(/^,/gm, '');
                  device_status = device_status.split(",");
                  device_r_await = device_status[4]
                  device_w_await = device_status[10]
                  device_util = device_status[21]

                    if(value.indexOf("Cycle_Count") !== -1){
                          devicepowercycle = device[4].replace(/^\s*12[\s\S]*?\-\s*(\d+)[\s\S]*?\n/m, '$1次');

                          let devicepowerloss = '';
                          if(value.indexOf("Power_Loss") !== -1){
                              devicepowerloss = device[5].replace(/^174[\s\S]*?\-\s*(\d+)[\s\S]*?\n/m, ', 不安全断电$1次');
                          }

                          devicepowercount = `${devicepowercycle}${devicepowerloss}`;

                          value = `${devicemodel} | 容量: ${devicecapacity} | ${devicetemp}\n通电: ${devicepowercount}, 累计${devicepowerhour}小时 | I/O: 读延迟${device_r_await}ms, 写延迟${device_w_await}ms, 负载${device_util}%\n`;
                    } else {
                          value = `${devicemodel} | 容量: ${devicecapacity} | 通电: ${devicepowerhour}小时 | I/O: 读延迟${device_r_await}ms, 写延迟${device_w_await}ms, 负载${device_util}% | ${devicetemp}\n`;
                    }
                }
                return value.replace(/\n/g, '<br>');
            } else {
                return `提示: 未安装硬盘或已直通硬盘控制器`;
            }
        }
    },
    {
        itemId: 'hdd2-status',
        colspan: 2,
        printBar: false,
        title: gettext('SATA硬盘 2'),
        textField: 'hdd2_status',
        renderer:function(value){
            if (value.length > 0) {
                let devices = value.matchAll(/(\s*Model.*:\s*[\s\S]*?\n){1,2}^User.*\[([\s\S]*?)\]\n^\s*9[\s\S]*?\-\s*(\d+)[\s\S]*?\n(^\s*12[\s\S]*?\-\s*\d+[\s\S]*?\n){0,1}(^174[\s\S]*?\-\s*\d+[\s\S]*?\n){0,1}(^19[0,4][\s\S]*?$){0,2}\nsd\S+((\s*?\d+\.\d{2}){22})/gm);
                for (const device of devices) {
                    if(device[1].indexOf("Family") !== -1){
                          devicemodel = device[1].replace(/.*Model Family:\s*([\s\S]*?)\n^Device Model:\s*([\s\S]*?)\n/m, '$1 - $2');
                    } else {
                          devicemodel = device[1].replace(/.*Model:\s*([\s\S]*?)\n/m, '$1');
                    }

                    devicecapacity = device[2].replace(/ |,/gm, '');

                    if (value.indexOf("Temperature") !== -1){
                          devicetemp = device[6].replace(/19[0,4].*[\-|In_the_past]\s*(\d+).*/m, '温度: $1°C');
                    } else {
                          devicetemp = `提示: 未检测到温度传感器`;
                    }

                    devicepowerhour = device[3].replace(/ |,/gm, '');

                  device_status = device[7].replace(/\s+/gm, ',');
                  device_status = device_status.replace(/^,/gm, '');
                  device_status = device_status.split(",");
                  device_r_await = device_status[4]
                  device_w_await = device_status[10]
                  device_util = device_status[21]

                    if(value.indexOf("Cycle_Count") !== -1){
                          devicepowercycle = device[4].replace(/^\s*12[\s\S]*?\-\s*(\d+)[\s\S]*?\n/m, '$1次');

                          let devicepowerloss = '';
                          if(value.indexOf("Power_Loss") !== -1){
                              devicepowerloss = device[5].replace(/^174[\s\S]*?\-\s*(\d+)[\s\S]*?\n/m, ', 不安全断电$1次');
                          }

                          devicepowercount = `${devicepowercycle}${devicepowerloss}`;

                          value = `${devicemodel} | 容量: ${devicecapacity} | ${devicetemp}\n通电: ${devicepowercount}, 累计${devicepowerhour}小时 | I/O: 读延迟${device_r_await}ms, 写延迟${device_w_await}ms, 负载${device_util}%\n`;
                    } else {
                          value = `${devicemodel} | 容量: ${devicecapacity} | 通电: ${devicepowerhour}小时 | I/O: 读延迟${device_r_await}ms, 写延迟${device_w_await}ms, 负载${device_util}% | ${devicetemp}\n`;
                    }
                }
                return value.replace(/\n/g, '<br>');
            } else {
                return `提示: 未安装硬盘或已直通硬盘控制器`;
            }
        }
    },
    {
        itemId: 'hdd3-status',
        colspan: 2,
        printBar: false,
        title: gettext('SATA硬盘 3'),
        textField: 'hdd3_status',
        renderer:function(value){
            if (value.length > 0) {
                let devices = value.matchAll(/(\s*Model.*:\s*[\s\S]*?\n){1,2}^User.*\[([\s\S]*?)\]\n^\s*9[\s\S]*?\-\s*(\d+)[\s\S]*?\n(^\s*12[\s\S]*?\-\s*\d+[\s\S]*?\n){0,1}(^174[\s\S]*?\-\s*\d+[\s\S]*?\n){0,1}(^19[0,4][\s\S]*?$){0,2}\nsd\S+((\s*?\d+\.\d{2}){22})/gm);
                for (const device of devices) {
                    if(device[1].indexOf("Family") !== -1){
                          devicemodel = device[1].replace(/.*Model Family:\s*([\s\S]*?)\n^Device Model:\s*([\s\S]*?)\n/m, '$1 - $2');
                    } else {
                          devicemodel = device[1].replace(/.*Model:\s*([\s\S]*?)\n/m, '$1');
                    }

                    devicecapacity = device[2].replace(/ |,/gm, '');

                    if (value.indexOf("Temperature") !== -1){
                          devicetemp = device[6].replace(/19[0,4].*[\-|In_the_past]\s*(\d+).*/m, '温度: $1°C');
                    } else {
                          devicetemp = `提示: 未检测到温度传感器`;
                    }

                    devicepowerhour = device[3].replace(/ |,/gm, '');

                  device_status = device[7].replace(/\s+/gm, ',');
                  device_status = device_status.replace(/^,/gm, '');
                  device_status = device_status.split(",");
                  device_r_await = device_status[4]
                  device_w_await = device_status[10]
                  device_util = device_status[21]

                    if(value.indexOf("Cycle_Count") !== -1){
                          devicepowercycle = device[4].replace(/^\s*12[\s\S]*?\-\s*(\d+)[\s\S]*?\n/m, '$1次');

                          let devicepowerloss = '';
                          if(value.indexOf("Power_Loss") !== -1){
                              devicepowerloss = device[5].replace(/^174[\s\S]*?\-\s*(\d+)[\s\S]*?\n/m, ', 不安全断电$1次');
                          }

                          devicepowercount = `${devicepowercycle}${devicepowerloss}`;

                          value = `${devicemodel} | 容量: ${devicecapacity} | ${devicetemp}\n通电: ${devicepowercount}, 累计${devicepowerhour}小时 | I/O: 读延迟${device_r_await}ms, 写延迟${device_w_await}ms, 负载${device_util}%\n`;
                    } else {
                          value = `${devicemodel} | 容量: ${devicecapacity} | 通电: ${devicepowerhour}小时 | I/O: 读延迟${device_r_await}ms, 写延迟${device_w_await}ms, 负载${device_util}% | ${devicetemp}\n`;
                    }
                }
                return value.replace(/\n/g, '<br>');
            } else {
                return `提示: 未安装硬盘或已直通硬盘控制器`;
            }
        }
    },
    {
        itemId: 'hdd4-status',
        colspan: 2,
        printBar: false,
        title: gettext('SATA硬盘 4'),
        textField: 'hdd4_status',
        renderer:function(value){
            if (value.length > 0) {
                let devices = value.matchAll(/(\s*Model.*:\s*[\s\S]*?\n){1,2}^User.*\[([\s\S]*?)\]\n^\s*9[\s\S]*?\-\s*(\d+)[\s\S]*?\n(^\s*12[\s\S]*?\-\s*\d+[\s\S]*?\n){0,1}(^174[\s\S]*?\-\s*\d+[\s\S]*?\n){0,1}(^19[0,4][\s\S]*?$){0,2}\nsd\S+((\s*?\d+\.\d{2}){22})/gm);
                for (const device of devices) {
                    if(device[1].indexOf("Family") !== -1){
                          devicemodel = device[1].replace(/.*Model Family:\s*([\s\S]*?)\n^Device Model:\s*([\s\S]*?)\n/m, '$1 - $2');
                    } else {
                          devicemodel = device[1].replace(/.*Model:\s*([\s\S]*?)\n/m, '$1');
                    }

                    devicecapacity = device[2].replace(/ |,/gm, '');

                    if (value.indexOf("Temperature") !== -1){
                          devicetemp = device[6].replace(/19[0,4].*[\-|In_the_past]\s*(\d+).*/m, '温度: $1°C');
                    } else {
                          devicetemp = `提示: 未检测到温度传感器`;
                    }

                    devicepowerhour = device[3].replace(/ |,/gm, '');

                  device_status = device[7].replace(/\s+/gm, ',');
                  device_status = device_status.replace(/^,/gm, '');
                  device_status = device_status.split(",");
                  device_r_await = device_status[4]
                  device_w_await = device_status[10]
                  device_util = device_status[21]

                    if(value.indexOf("Cycle_Count") !== -1){
                          devicepowercycle = device[4].replace(/^\s*12[\s\S]*?\-\s*(\d+)[\s\S]*?\n/m, '$1次');

                          let devicepowerloss = '';
                          if(value.indexOf("Power_Loss") !== -1){
                              devicepowerloss = device[5].replace(/^174[\s\S]*?\-\s*(\d+)[\s\S]*?\n/m, ', 不安全断电$1次');
                          }

                          devicepowercount = `${devicepowercycle}${devicepowerloss}`;

                          value = `${devicemodel} | 容量: ${devicecapacity} | ${devicetemp}\n通电: ${devicepowercount}, 累计${devicepowerhour}小时 | I/O: 读延迟${device_r_await}ms, 写延迟${device_w_await}ms, 负载${device_util}%\n`;
                    } else {
                          value = `${devicemodel} | 容量: ${devicecapacity} | 通电: ${devicepowerhour}小时 | I/O: 读延迟${device_r_await}ms, 写延迟${device_w_await}ms, 负载${device_util}% | ${devicetemp}\n`;
                    }
                }
                return value.replace(/\n/g, '<br>');
            } else {
                return `提示: 未安装硬盘或已直通硬盘控制器`;
            }
        }
    },
    {
        itemId: 'hdd5-status',
        colspan: 2,
        printBar: false,
        title: gettext('SATA硬盘 5'),
        textField: 'hdd5_status',
        renderer:function(value){
            if (value.length > 0) {
                let devices = value.matchAll(/(\s*Model.*:\s*[\s\S]*?\n){1,2}^User.*\[([\s\S]*?)\]\n^\s*9[\s\S]*?\-\s*(\d+)[\s\S]*?\n(^\s*12[\s\S]*?\-\s*\d+[\s\S]*?\n){0,1}(^174[\s\S]*?\-\s*\d+[\s\S]*?\n){0,1}(^19[0,4][\s\S]*?$){0,2}\nsd\S+((\s*?\d+\.\d{2}){22})/gm);
                for (const device of devices) {
                    if(device[1].indexOf("Family") !== -1){
                          devicemodel = device[1].replace(/.*Model Family:\s*([\s\S]*?)\n^Device Model:\s*([\s\S]*?)\n/m, '$1 - $2');
                    } else {
                          devicemodel = device[1].replace(/.*Model:\s*([\s\S]*?)\n/m, '$1');
                    }

                    devicecapacity = device[2].replace(/ |,/gm, '');

                    if (value.indexOf("Temperature") !== -1){
                          devicetemp = device[6].replace(/19[0,4].*[\-|In_the_past]\s*(\d+).*/m, '温度: $1°C');
                    } else {
                          devicetemp = `提示: 未检测到温度传感器`;
                    }

                    devicepowerhour = device[3].replace(/ |,/gm, '');

                  device_status = device[7].replace(/\s+/gm, ',');
                  device_status = device_status.replace(/^,/gm, '');
                  device_status = device_status.split(",");
                  device_r_await = device_status[4]
                  device_w_await = device_status[10]
                  device_util = device_status[21]

                    if(value.indexOf("Cycle_Count") !== -1){
                          devicepowercycle = device[4].replace(/^\s*12[\s\S]*?\-\s*(\d+)[\s\S]*?\n/m, '$1次');

                          let devicepowerloss = '';
                          if(value.indexOf("Power_Loss") !== -1){
                              devicepowerloss = device[5].replace(/^174[\s\S]*?\-\s*(\d+)[\s\S]*?\n/m, ', 不安全断电$1次');
                          }

                          devicepowercount = `${devicepowercycle}${devicepowerloss}`;

                          value = `${devicemodel} | 容量: ${devicecapacity} | ${devicetemp}\n通电: ${devicepowercount}, 累计${devicepowerhour}小时 | I/O: 读延迟${device_r_await}ms, 写延迟${device_w_await}ms, 负载${device_util}%\n`;
                    } else {
                          value = `${devicemodel} | 容量: ${devicecapacity} | 通电: ${devicepowerhour}小时 | I/O: 读延迟${device_r_await}ms, 写延迟${device_w_await}ms, 负载${device_util}% | ${devicetemp}\n`;
                    }
                }
                return value.replace(/\n/g, '<br>');
            } else {
                return `提示: 未安装硬盘或已直通硬盘控制器`;
            }
        }
    }

image-20220722150837107

重启服务

systemctl restart pveproxy