发现Typecho后台服务器状态插件

在翻Typecho插件的时候,我偶然发现了两双筷子写的Typecho后台服务器状态查看插件,于是就尝试了一下下。
这个插件在桌面端网页没问题,感觉还挺不错的。
x_20250508_161758.png
但是一旦变成移动端,后台的布局就会出现问题,逼死强迫症。
Screenshot_20250508-161701_Firefox.png
而且,CPU利用率有时候还会变成0.9999999999%这种离谱的数据,我寻思着就不能取两位小数好看一点吗?
于是,我决定,自己修改代码,把服务器信息移动到页面底部的位置,并且修改显示数据的逻辑,让信息看着更加舒适一点。
(其实也不完全是我写,是面向LLM编程 X)

修改代码的过程

首先,需要把注册方法的地方改为页面底部

Typecho_Plugin::factory('admin/footer.php')->begin = array('LinuxInfo_Plugin', 'render');

然后就是修改一下插入的样式:

echo <<<STR
<div style="color: #999; line-height: 1.8; text-align: center;">
$info_str
</div>
<div style="height:40px;display:block;"></div>
STR;

因为我觉的系统信息用鼠标悬浮预览不明显,于是修改为直接独立一行把信息全部展示:

return <<<SYS_INFO
服务器信息:
<br>持续运行:{$uptime},Web服务器:{$_SERVER['SERVER_SOFTWARE']},PHP 版本:{$php['version']},SAPI 接口:{$php['sapi']},PHP内存限制:{$php['mem_max']},上传文件限制:{$php['up_max']}
SYS_INFO;

以及让LLM帮忙完善了CPU信息获取的逻辑:

private static function cpu_info(){
    function GetCoreInformation() {
        $data = file('/proc/stat');
        $cores = [];
        foreach( $data as $line ) {
            if( preg_match('/^cpu[0-9]/', $line) ) {
                $info = explode(' ', $line);
                $cores[] = [
                    'user' => $info[1],
                    'nice' => $info[2],
                    'sys' => $info[3],
                    'idle' => $info[4],
                    'iowait' => $info[5],
                    'irq' => $info[6],
                    'softirq' => $info[7]
                ];
            }
        }
        return $cores;
    }

    function GetCpuPercentages($stat1, $stat2) {
        if(count($stat1) !== count($stat2)){
            return;
        }
        $cpus = [];
        for( $i = 0, $l = count($stat1); $i < $l; $i++) {
            $dif = [];
            $dif['user'] = $stat2[$i]['user'] - $stat1[$i]['user'];
            $dif['nice'] = $stat2[$i]['nice'] - $stat1[$i]['nice'];
            $dif['sys'] = $stat2[$i]['sys'] - $stat1[$i]['sys'];
            $dif['idle'] = $stat2[$i]['idle'] - $stat1[$i]['idle'];
            $dif['iowait'] = $stat2[$i]['iowait'] - $stat1[$i]['iowait'];
            $dif['irq'] = $stat2[$i]['irq'] - $stat1[$i]['irq'];
            $dif['softirq'] = $stat2[$i]['softirq'] - $stat1[$i]['softirq'];
            $total = array_sum($dif);
            $cpu = [];
            foreach($dif as $x=>$y) $cpu[$x] = round($y / $total * 100, 2);
            $cpus['CPU' . $i] = $cpu;
        }
        return $cpus;
    }

    $stat1 = GetCoreInformation();
    sleep(1);
    $stat2 = GetCoreInformation();
    $data = GetCpuPercentages($stat1, $stat2);

    $overall_cpu_usage = 0;
    $core_details = [];

    foreach ($data as $core => $usage) {
        $core_usage = 100 - $usage['idle'];
        $overall_cpu_usage += $core_usage;
        $core_details[] = "{$core}: " . number_format($core_usage, 2) . "%";
    }

    $overall_cpu_usage = round($overall_cpu_usage / count($data), 2);
    $core_details_str = implode(", ", $core_details);

    $sys_load = sys_getloadavg();
    $sys_load_1min = number_format($sys_load[0], 3);
    $sys_load_5min = number_format($sys_load[1], 3);
    $sys_load_15min = number_format($sys_load[2], 3);

    return <<<CPU_INFO
    <span style="cursor: default" title="CPU使用:{$core_details_str}
    系统负载:1分钟:{$sys_load_1min} 5分钟:{$sys_load_5min} 15分钟:{$sys_load_15min}
    ">CPU: {$overall_cpu_usage} %</span>
    CPU_INFO;
}

原来的逻辑只是判断单个CPU的占有率,当CPU数量为多个的时候就会不准确,这里我优化成了整体利用率,并且悬停时会显示每个CPU的占用率信息。

改造效果

虽然不是彻底重构,但是我对改造后的插件还是比较满意的。
x_20250508_163941.png
手机的网页端也舒服了。
Screenshot_20250508-164058_Firefox.png

修改后的插件分享

修改后的插件已经放到了GitHub:Typecho-LinuxInfo
反正源码也不多,我这里也可以直接放出来:
Plugin.php:

<?php
if (!defined('__TYPECHO_ROOT_DIR__')) exit;
/**
 * 查看系统信息,仅限 Linux 系统,基于<a href="https://www.dbkuaizi.com" target="_blank">两双筷子</a>的插件进行二次开发,<a href="https://github.com/Chocola-X/Typecho-LinuxInfo" target="_blank">使用帮助</a>
 *
 * @package LinuxInfo
 * @author GTX690战术核显卡导弹
 * @version 1.1.0
 * @link https://www.nekopara.uk
 */
class LinuxInfo_Plugin implements Typecho_Plugin_Interface
{
    // 插件名称
    protected static $plugin_name = 'LinuxInfo';

    /**
     * 激活插件方法,如果激活失败,直接抛出异常
     *
     * @access public
     * @return void
     * @throws Typecho_Plugin_Exception
     */
    public static function activate()
    {
        // 如果当前系统不是 linux 则抛出错误
        if(PHP_OS != 'Linux') {
            throw new Typecho_Plugin_Exception(_t("此插件仅支持 Linux 系统"));
        }
        // 在后端页面底部 注册方法
        Typecho_Plugin::factory('admin/footer.php')->begin = array('LinuxInfo_Plugin', 'render');
    }

    /**
     * 禁用插件方法,如果禁用失败,直接抛出异常
     *
     * @static
     * @access public
     * @return void
     * @throws Typecho_Plugin_Exception
     */
    public static function deactivate(){}

    /**
     * 获取插件配置面板
     *
     * @access public
     * @param Typecho_Widget_Helper_Form $form 配置面板
     * @return void
     */
    public static function config(Typecho_Widget_Helper_Form $form)
    {
        /** 分类名称 */
        $show_item = array(
            'cpu_info'    =>  _t('CPU使用情况') . '( CPU 使用率、系统负载)',
                           'mem_info'    =>  _t('内存使用情况') . '(内存使用率、SWAP交换分区)',
                           'disk_info'    =>  _t('磁盘使用情况') . '(磁盘使用率、磁盘已用空间、磁盘总空间)',
                           'sys_info'    =>  _t('服务器信息') . ' (系统运行时间、服务器软件、PHP 版本、SAPI 接口、PHP 内存限制、上传文件限制)',
        );

        // 设置默认选中
        $show_option_value = ['cpu_info','mem_info','disk_info'];
        $ShowInfo = new Typecho_Widget_Helper_Form_Element_Checkbox('show_items',$show_item, $show_option_value, _t('选择显示系统信息'));
        $form->addInput($ShowInfo->multiMode());

        $separator = new Typecho_Widget_Helper_Form_Element_Text('separator', NULL, ' ★ ', _t('分隔符'), '设置数据项分割字符');
        $form->addInput($separator);
    }

    /**
     * 个人用户的配置面板
     *
     * @access public
     * @param Typecho_Widget_Helper_Form $form
     * @return void
     */
    public static function personalConfig(Typecho_Widget_Helper_Form $form){}

    /**
     * 插件实现方法
     *
     * @access public
     * @return void
     */
    public static function render()
    {
        $show_items = Typecho_Widget::widget('Widget_Options')->plugin(self::$plugin_name)->show_items;
        $separator = Typecho_Widget::widget('Widget_Options')->plugin(self::$plugin_name)->separator;

        // 获取 linux 系统消息
        $info_str = '';
        $show_items_max = count($show_items) - 1;
        foreach ($show_items as $show_item_k => $show_item) {
            $method = $show_item;
            if (method_exists(__CLASS__, $method)) {
                $info_str .= self::$method();
            }
            if ($show_items_max != $show_item_k) {
                $info_str .= $separator;
            }
        }

        echo <<<STR
        <div style="color: #999; line-height: 1.8; text-align: center;">
        $info_str
        </div>
        <div style="height:40px;display:block;"></div>
        STR;
    }

    // 获取CPU信息
    private static function cpu_info(){
        function GetCoreInformation() {
            $data = file('/proc/stat');
            $cores = [];
            foreach( $data as $line ) {
                if( preg_match('/^cpu[0-9]/', $line) ) {
                    $info = explode(' ', $line);
                    $cores[] = [
                        'user' => $info[1],
                        'nice' => $info[2],
                        'sys' => $info[3],
                        'idle' => $info[4],
                        'iowait' => $info[5],
                        'irq' => $info[6],
                        'softirq' => $info[7]
                    ];
                }
            }
            return $cores;
        }

        function GetCpuPercentages($stat1, $stat2) {
            if(count($stat1) !== count($stat2)){
                return;
            }
            $cpus = [];
            for( $i = 0, $l = count($stat1); $i < $l; $i++) {
                $dif = [];
                $dif['user'] = $stat2[$i]['user'] - $stat1[$i]['user'];
                $dif['nice'] = $stat2[$i]['nice'] - $stat1[$i]['nice'];
                $dif['sys'] = $stat2[$i]['sys'] - $stat1[$i]['sys'];
                $dif['idle'] = $stat2[$i]['idle'] - $stat1[$i]['idle'];
                $dif['iowait'] = $stat2[$i]['iowait'] - $stat1[$i]['iowait'];
                $dif['irq'] = $stat2[$i]['irq'] - $stat1[$i]['irq'];
                $dif['softirq'] = $stat2[$i]['softirq'] - $stat1[$i]['softirq'];
                $total = array_sum($dif);
                $cpu = [];
                foreach($dif as $x=>$y) $cpu[$x] = round($y / $total * 100, 2);
                $cpus['CPU' . $i] = $cpu;
            }
            return $cpus;
        }

        $stat1 = GetCoreInformation();
        sleep(1);
        $stat2 = GetCoreInformation();
        $data = GetCpuPercentages($stat1, $stat2);

        $overall_cpu_usage = 0;
        $core_details = [];

        foreach ($data as $core => $usage) {
            $core_usage = 100 - $usage['idle'];
            $overall_cpu_usage += $core_usage;
            $core_details[] = "{$core}: " . number_format($core_usage, 2) . "%";
        }

        $overall_cpu_usage = round($overall_cpu_usage / count($data), 2);
        $core_details_str = implode(", ", $core_details);

        $sys_load = sys_getloadavg();
        $sys_load_1min = number_format($sys_load[0], 3);
        $sys_load_5min = number_format($sys_load[1], 3);
        $sys_load_15min = number_format($sys_load[2], 3);

        return <<<CPU_INFO
        <span style="cursor: default" title="CPU使用:{$core_details_str}
        系统负载:1分钟:{$sys_load_1min} 5分钟:{$sys_load_5min} 15分钟:{$sys_load_15min}
        ">CPU: {$overall_cpu_usage} %</span>
        CPU_INFO;
    }

    // 获取内存信息
    private static function mem_info(){
        if (false === ($str = @file("/proc/meminfo"))) return false;

        $str = implode("", $str);

        preg_match_all("/MemTotal\s{0,}\:+\s{0,}([\d\.]+).+?MemFree\s{0,}\:+\s{0,}([\d\.]+).+?Cached\s{0,}\:+\s{0,}([\d\.]+).+?SwapTotal\s{0,}\:+\s{0,}([\d\.]+).+?SwapFree\s{0,}\:+\s{0,}([\d\.]+)/s", $str, $buf);
        preg_match_all("/Buffers\s{0,}\:+\s{0,}([\d\.]+)/s", $str, $buffers);
        $mem_info['memTotal'] = round($buf[1][0]/1024, 2);

        $mem_info['memFree'] = round($buf[2][0]/1024, 2);

        $mem_info['memBuffers'] = round($buffers[1][0]/1024, 2);
        $mem_info['memCached'] = round($buf[3][0]/1024, 2);

        $mem_info['memUsed'] = $mem_info['memTotal']-$mem_info['memFree'];

        $mem_info['memPercent'] = (floatval($mem_info['memTotal'])!=0)?round($mem_info['memUsed']/$mem_info['memTotal']*100,2):0;

        $mem_info['memRealUsed'] = $mem_info['memTotal'] - $mem_info['memFree'] - $mem_info['memCached'] - $mem_info['memBuffers']; //真实内存使用
        $mem_info['memRealFree'] = $mem_info['memTotal'] - $mem_info['memRealUsed']; //真实空闲
        $mem_info['memRealPercent'] = (floatval($mem_info['memTotal'])!=0)?round($mem_info['memRealUsed']/$mem_info['memTotal']*100,2):0; //真实内存使用率

        $mem_info['memCachedPercent'] = (floatval($mem_info['memCached'])!=0)?round($mem_info['memCached']/$mem_info['memTotal']*100,2):0; //Cached内存使用率

        $mem_info['swapTotal'] = round($buf[4][0]/1024, 2);

        $mem_info['swapFree'] = round($buf[5][0]/1024, 2);

        $mem_info['swapUsed'] = round($mem_info['swapTotal']-$mem_info['swapFree'], 2);

        $mem_info['swapPercent'] = (floatval($mem_info['swapTotal'])!=0)?round($mem_info['swapUsed']/$mem_info['swapTotal']*100,2):0;
        $mem_per = round($mem_info['memRealUsed'] / $mem_info['memTotal'] * 100, 2);
        return <<<MEM_INFO
        <span style="cursor: default" title="内存占用:{$mem_info['memRealUsed']} MB / {$mem_info['memTotal']} MB
        交换空间占用:{$mem_info['swapUsed']} MB / {$mem_info['swapTotal']} MB
        ">内存: {$mem_per} %</span>
        MEM_INFO;
    }

    // 获取磁盘信息
    private static function disk_info(){
        $dt = round(@disk_total_space(".")/(1024*1024*1024), 2); //总
        $df = round(@disk_free_space(".")/(1024*1024*1024), 2); //可用
        $du = $dt-$df; //已用
        $disk_use = round($du / $dt * 100, 2);
        return <<<DISK_INFO
        <span style="cursor: default" title="已用硬盘空间:{$du} GB / {$dt} GB">硬盘: {$disk_use} %</span>
        DISK_INFO;
    }

    // 获取 系统信息
    private static function sys_info()
    {
        if (false === ($str = @file("/proc/uptime"))) return false;

        $str = explode(" ", implode("", $str));

        $str = trim($str[0]);

        $min = $str / 60;

        $hours = $min / 60;

        $days = floor($hours / 24);

        $hours = floor($hours - ($days * 24));

        $min = floor($min - ($days * 60 * 24) - ($hours * 60));

        if ($days !== 0) $uptime = $days."天";

        if ($hours !== 0) $uptime .= $hours."小时";

        $uptime .= $min."分钟";
        $php['version'] = PHP_VERSION;
        $php['sapi'] = php_sapi_name();
        $php['mem_max'] = get_cfg_var('memory_limit');
        $php['up_max'] = get_cfg_var('upload_max_filesize');

        return <<<SYS_INFO
        服务器信息:
        <br>持续运行:{$uptime},Web服务器:{$_SERVER['SERVER_SOFTWARE']},PHP 版本:{$php['version']},SAPI 接口:{$php['sapi']},PHP内存限制:{$php['mem_max']},上传文件限制:{$php['up_max']}
        SYS_INFO;

    }
}

优先使用Github上面的吧,万一那天我修复一些问题,可能只会修改Github上面的仓库的内容而忘记修改这篇文章。
当然,我希望以后不会出现问题。

使用方法

将GitHub上面的Plugin.php或者这个页面的Plugin.php内容保存下载,放到Typecho的插件目录usr/plugins/下的LinuxInfo文件夹(需要自己创建),例如:typecho/usr/plugins/LinuxInfo/Plugin.php,进入Typecho后台启用插件即可。
默认不显示系统详细信息,需要自己勾选显示才会展示。
不过,需要注意的是,这个插件仅可用于Linux系统,Windows系统不适用。谁用Windows做服务器啊?!!不会吧不会吧?
最后还是要感谢一下两双筷子写出来这个有意思的插件。