三 文章首页 实时留言 网络邻居 开往 虫洞
返回

如何自制一个简易的资源监控程序

2024-12-29 00:09:39
分类: Linux 标签: 系统运维,Bun

前段时间有网友留言好奇我这个博客下的资源监控是怎么实现的,今天就和大家讲一下实现思路。

这里博主使用 JS / TS(Bun)作为演示。当然,你也可以选择自己喜欢的编程语言来实现,但本质上是通用的。

首先科普一下 Linux 下的 /proc/ 这个目录,这里存放着很多内核级的接口文件,有些是可读或可写的(或需要管理员权限)。

下面是几种常见的:

文件信息
/proc/cpuinfo这里可以获取到 CPU 型号,频率等信息。
/proc/meminfo可以获取到内存有关的详细信息。
/proc/net/dev可以获取到网卡的上下行流量,数据包。
/proc/stat可以获取到 CPU 工作状态。

内存总量


为了获取内存总量,我们可以直接读取 /proc/meminfo 文件,过滤文本并找到 MemTotal 字段。

cat /proc/meminfo | grep 'MemTotal'

我们会获得这样的信息

MemTotal:        3878108 kB

如果直接获取到阈值,可以借助 awk 来辅助:

cat /proc/meminfo | grep 'MemTotal' | awk '{print $2}'

即可得到

3878108

这里的阈值 / 1024 即可获得 MB 单位。我这里是 4G内存的服务器,实际可用为 3787 MB,如果想获得 GB 或更高单位,直接以倍数计算即可。

内存剩余量


如果想要获取内存余量,和上面的思路一致,但我们只需要获取 MemFree 字段,以及 Cached 字段即可得出。

可能有疑问,为什么还要获取 Cached 呢?这是因为 MemFree 不包含缓存,只计算了未使用的内存空间。

一般情况下,剩余内存 = 空闲内存 + 缓存 + Buffers

(而其中 Buffers 基本很少,也可以忽略不计)

CPU 使用率


为了得到 CPU 使用率,首先读取 /proc/stat 文件,过滤出其中每个核心的 阈值文本。

使用 shell 命令: cat /proc/stat 会得到类似以下信息:

cpu  185061 18 194351 32867149 40905 0 5423 0 0 0
cpu0 93003 10 97432 16434349 20930 0 2432 0 0 0
cpu1 92057 8 96918 16432799 19975 0 2991 0 0 0
.....还有很多无关内容

其中咱们只需要关注和 cpu 有关的字段,其中第一个 cpu 字段代表所有核心的总量

对应的阈值是:

usernicesystemidleiowaitirqsoftirqsteal
cpu18506118194351328671494090505423
cpu0930031097432164343492093002432
cpu192057896918164327991997502991

CPU 后面带数字的是不同的核心,我这里是双核服务器所以是从 cpu0 到 cpu1.

为了确保算出CPU使用率,我们需要一个时间差来计算 CPU 状态,假如设定为 1 秒

计算公式:

总时间:每个核心的 user + nice + system + idle + irq 的和
总空闲时间:每个核心的 idle 的和
总时间差 = 1秒后CPU总时间1秒前CPU总时间
总空闲时间差 = 1秒后总空闲时间差1秒前总空闲时间差
CPU 使用率 = (总时间差 - 总空闲时间差)总时间差 乘 100

这里我为了代码简短,就不读文件过滤文本了,使用 os 模块可直接获取所有 cpu 信息

// 引入 os 模块来获取 cpus 
import os from 'os'

// 这里我们创建一个获取CPU状态的函数
function getCPUinfo(){
    // 初始一个临时模板对象
    let _cpu = { total:0,user:0,nice:0,sys:0,idle:0,irq:0 }
    // 遍历每个 cpu 状态,并递增阈值
    for(const cpu of os.cpus()){
        _cpu.user += cpu.times.user;
        _cpu.nice += cpu.times.nice;
        _cpu.sys += cpu.times.sys;
        _cpu.idle += cpu.times.idle;
        _cpu.irq += cpu.times.irq;
    }
    // 设置总时间
    _cpu.total = _cpu.user + _cpu.nice + _cpu.sys + _cpu.idle + _cpu.irq
    // 最终返回全部 cpu 核心递增的信息
    return {
        total: _cpu.total,
        user: _cpu.user,
        nice: _cpu.nice,
        sys: _cpu.sys,
        idle: _cpu.idle,
        irq: _cpu.irq
    }
}

以上定义了基本获取CPU信息的函数,接下来我们再通过定时任务来计算时间差的结果

// 获取初始 cpu 状态
let old_cpu_info = getCPUinfo()
// 1秒后执行
setTimeout(()=>{
    // 获取1秒后 cpu 状态
    let new_cpu_info = getCPUinfo()
    // 计算这1秒的总时间差
    let total = new_cpu_info.total - old_cpu_info.total
    // 计算这1秒的空闲时间差
    let idle = new_cpu_info.idle - old_cpu_info.idle
    // 计算这1秒的CPU使用率
    let usage = (total - idle) / total * 100
    // 精确单位到 2 位小数
    console.log(`${usage.toFixed(2)} %`)
},1000)
watch bun run ./test.ts

咱们为了让这个脚本一直执行,可以使用 watch 命令来观察(它本身刚好是每1秒执行一次)。

网络上下行速率


读取 /proc/net/dev 文件,咱们会得到类似以下信息

Inter-|   Receive                                                |  Transmit
 face |bytes    packets errs drop fifo frame compressed multicast|bytes    packets errs drop fifo colls carrier compressed
    lo:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0
  eth0: 507213616 1580643    0    0    0     0          0         0 1201777570 1852460    0    0    0     0       0          0

其中我的网卡为 eth0 ,这个数据中 Receive 和 Transmit,代表者上行和下行流量。其中 bytes 代表总共多少字节单位数据。(它们会在重启系统后重新开始计数)而 packets 为数据包。我们重点关注 eth0,整成表格后为以下信息。

网络接口下载字节下载数据包上传字节上传数据包
eth0507213616158064312017775701852460

我们通过以下 shell 命令可以直接获取到 上行和下行的 bytes(其中 eth0 请替换成自己实际的网卡名称)

cat /proc/net/dev | sed -n '/eth0/p' | awk '{print $2, $10}

会得到该数据(左为下行,右为上行)

507213616 1201777570

接下来我们写一个脚本,也是类似上面计算CPU占用率一样,通过时间差来计算网卡速率。

计算公式:

每秒速率 = 1秒后的 bytes1秒前的 bytes
如果要得到 KB/s 单位,可除于 1024,以此类推。

let res = await Bun.$ `cat /proc/net/dev | sed -n '/eth0/p' | awk '{print $2, $10}'`.text()
let contextSplit = res.split(' ')
let downloadBytes = contextSplit[0]
let uploadBytes = contextSplit[1].trim()
setTimeout(async ()=>{
    let _res = await Bun.$ `cat /proc/net/dev | sed -n '/eth0/p' | awk '{print $2, $10}'`.text()
    let _contextSplit = _res.split(' ')
    // 获取1秒后下载的字节数
    let _downloadBytes = _contextSplit[0]
    // 获取1秒后上传的字节数
    let _uploadBytes = _contextSplit[1].trim()
    // 得出的下载的速率
    let download = ((Number(_downloadBytes) - Number(downloadBytes)) / 1024).toFixed(1)
    // 得出的上传的速率
    let upload = ((Number(_uploadBytes) - Number(uploadBytes)) / 1024).toFixed(1)
    console.log(download + ' KB/s')
    console.log(upload + ' KB/s')
},1000)

现在我们得到了(总内存量,CPU使用率,内存剩余量,以及缓存,网络速率)数据的获取和计算方式。接下来如何应用到其他方面,我知道你懂得 ^_^