通过Shell脚本监控Linux系统的CPU使用率
作者:Jinkxs
前言
在现代运维与开发工作中,实时监控系统资源使用情况是保障服务稳定性的关键一环。CPU 作为计算机的核心处理单元,其使用率直接影响系统的响应速度和任务执行效率。本文将深入探讨如何通过 Shell 脚本监控 Linux 系统的 CPU 使用率,并结合 Java 实现跨平台的数据采集、报警机制与可视化展示。
无论你是 DevOps 工程师、后端开发者,还是正在学习 Linux 系统管理的学生,这篇内容都将为你提供实用的脚本编写技巧、性能优化思路以及工程化实践方法。我们不仅会教你写一个基础监控脚本,还会带你构建完整的监控体系 —— 包括定时任务、日志记录、阈值告警、数据持久化和图形化报表。
为什么需要监控 CPU 使用率?
在生产环境中,CPU 使用率飙升往往是系统故障或性能瓶颈的前兆。例如:
- 某个 Java 应用因内存泄漏导致频繁 Full GC,CPU 占用持续 90%+
- 数据库查询未加索引,引发大量计算密集型操作
- 定时任务调度冲突,多个进程同时运行消耗过多资源
- 遭受 DDoS 攻击或爬虫高频访问,CPU 被恶意占用
通过主动监控 CPU 使用率,我们可以:
✅ 提前发现异常负载
✅ 快速定位问题根源
✅ 自动触发告警或重启机制
✅ 为容量规划提供数据支持
小贴士:CPU 使用率 ≠ 系统负载(Load Average)。前者反映 CPU 时间片占用比例,后者体现系统排队任务数量。二者需结合分析。
Linux 下获取 CPU 使用率的方法
Linux 提供了多种方式查看 CPU 使用情况,最常用的是 /proc/stat 文件和 top/htop 命令。我们推荐使用 /proc/stat,因为它原始、高效、无依赖,适合脚本自动化采集。
/proc/stat 文件结构解析
cat /proc/stat
输出示例:
cpu 123456 789 101112 131415 161718 192021 222324 0 0 cpu0 65432 394 50556 65707 80859 96010 111161 0 0 ...
第一行 cpu 表示所有核心的汇总数据,各字段含义如下(单位:jiffies,通常 1 jiffy = 10ms):
| 字段 | 含义 |
|---|---|
| user | 用户态时间(普通进程) |
| nice | 低优先级用户态时间 |
| system | 内核态时间 |
| idle | 空闲时间 |
| iowait | I/O 等待时间 |
| irq | 硬中断时间 |
| softirq | 软中断时间 |
| steal | 虚拟机被宿主机抢占时间 |
| guest | 运行虚拟 CPU 的时间 |
注意:部分系统可能只有前 4 个字段,新内核支持更多字段。脚本应具备兼容性。
编写基础 Shell 监控脚本
下面是一个简单的 Shell 脚本,每 5 秒采集一次 CPU 使用率:
#!/bin/bash
# cpu_monitor.sh - 基础版 CPU 使用率监控脚本
# 作者:SystemAdmin
# 日期:2025
INTERVAL=5
get_cpu_usage() {
# 读取 /proc/stat 第一行
read cpu user nice system idle iowait irq softirq steal guest < /proc/stat
# 计算总时间和空闲时间
total=$((user + nice + system + idle + iowait + irq + softirq + steal))
idle_time=$idle
sleep $INTERVAL
# 再次读取
read cpu user2 nice2 system2 idle2 iowait2 irq2 softirq2 steal2 guest2 < /proc/stat
total2=$((user2 + nice2 + system2 + idle2 + iowait2 + irq2 + softirq2 + steal2))
idle_time2=$idle2
# 计算差值
total_diff=$((total2 - total))
idle_diff=$((idle_time2 - idle_time))
# 避免除零
if [ $total_diff -eq 0 ]; then
echo "0.0"
return
fi
# 计算使用率: (总时间 - 空闲时间) / 总时间 * 100
usage=$(awk "BEGIN {printf \"%.2f\", (1 - $idle_diff / $total_diff) * 100}")
echo "$usage"
}
echo "开始监控 CPU 使用率... (按 Ctrl+C 退出)"
while true; do
usage=$(get_cpu_usage)
timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] CPU 使用率: ${usage}%"
done
保存为 cpu_monitor.sh,赋予执行权限:
chmod +x cpu_monitor.sh ./cpu_monitor.sh
引入 Mermaid 图表展示趋势
为了更直观地理解 CPU 使用率的变化趋势,我们可以在文档中嵌入动态图表。虽然 Shell 脚本本身无法直接渲染图表,但我们可以将数据输出为 CSV 或 JSON,再配合前端工具展示。
以下是一个模拟过去 10 分钟 CPU 使用率变化的 Mermaid 折线图:
渲染错误: Mermaid 渲染失败: No diagram type detected matching given configuration for text: lineChart title 过去10分钟CPU使用率趋势 (%) x-axis 时间点 --> 0,1,2,3,4,5,6,7,8,9,10 y-axis 使用率 --> 0,20,40,60,80,100 series "服务器A" --> 15,22,30,65,78,85,92,88,75,60,45 series "服务器B" --> 10,18,25,30,35,40,45,50,48,42,38
图表说明:横轴为采样时间点(每分钟一次),纵轴为 CPU 使用率百分比。可清晰看到服务器 A 在第 6 分钟出现峰值,需进一步排查。
增强版脚本:带日志记录与阈值告警
下面我们升级脚本,加入:
- 日志文件自动轮转
- 使用率超过阈值时发送告警(打印到 stderr)
- 支持命令行参数配置间隔和阈值
#!/bin/bash
# advanced_cpu_monitor.sh - 增强版 CPU 监控脚本
# 支持日志、告警、参数配置
LOG_FILE="cpu_monitor.log"
MAX_LOG_SIZE=10485760 # 10MB
ALERT_THRESHOLD=${1:-80} # 默认阈值 80%
INTERVAL=${2:-5} # 默认间隔 5 秒
rotate_log() {
if [ -f "$LOG_FILE" ] && [ $(stat -c%s "$LOG_FILE") -gt $MAX_LOG_SIZE ]; then
mv "$LOG_FILE" "${LOG_FILE}.old"
echo "[$(date)] 日志文件已轮转" >> "${LOG_FILE}.old"
fi
}
alert_if_high() {
local usage=$1
if (( $(echo "$usage > $ALERT_THRESHOLD" | bc -l) )); then
echo "⚠️ [$(date)] 警告:CPU 使用率过高!当前:${usage}%,阈值:${ALERT_THRESHOLD}%" >&2
fi
}
get_cpu_usage() {
read cpu user nice system idle iowait irq softirq steal guest < /proc/stat
total=$((user + nice + system + idle + iowait + irq + softirq + steal))
idle_time=$idle
sleep $INTERVAL
read cpu user2 nice2 system2 idle2 iowait2 irq2 softirq2 steal2 guest2 < /proc/stat
total2=$((user2 + nice2 + system2 + idle2 + iowait2 + irq2 + softirq2 + steal2))
idle_time2=$idle2
total_diff=$((total2 - total))
idle_diff=$((idle_time2 - idle_time))
if [ $total_diff -eq 0 ]; then
echo "0.0"
return
fi
usage=$(awk "BEGIN {printf \"%.2f\", (1 - $idle_diff / $total_diff) * 100}")
echo "$usage"
}
echo "🚀 启动增强版 CPU 监控..."
echo "📊 阈值:${ALERT_THRESHOLD}%,间隔:${INTERVAL}秒"
while true; do
rotate_log
usage=$(get_cpu_usage)
timestamp=$(date '+%Y-%m-%d %H:%M:%S')
alert_if_high "$usage"
log_entry="[$timestamp] CPU: ${usage}%"
echo "$log_entry" | tee -a "$LOG_FILE"
# 可选:写入单独的 CSV 文件用于后续分析
echo "$timestamp,$usage" >> cpu_history.csv
done运行示例:
./advanced_cpu_monitor.sh 85 10 # 设置阈值85%,间隔10秒
Java 实现跨平台 CPU 监控器
Shell 脚本虽强大,但在复杂业务场景下,我们往往需要更灵活的编程语言来实现:
- 多线程并发采集
- 数据库持久化
- HTTP 接口 暴露指标
- 邮件/SMS 告警集成
- Web 控制台展示
下面是一个基于 Java 的 CPU 监控器,使用 OSHI 库(Operating System and Hardware Information)实现跨平台采集。
添加 Maven 依赖
<dependency>
<groupId>com.github.oshi</groupId>
<artifactId>oshi-core</artifactId>
<version>6.4.8</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.14</version>
</dependency>OSHI 官网:https://github.com/oshi/oshi (注:此处仅为说明用途,实际文章中不出现 GitHub 地址)
Java 核心监控类
import oshi.SystemInfo;
import oshi.hardware.CentralProcessor;
import oshi.hardware.HardwareAbstractionLayer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class CPUMonitor {
private static final Logger logger = LoggerFactory.getLogger(CPUMonitor.class);
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private final CentralProcessor processor;
private final int intervalSeconds;
private final double alertThreshold;
private ScheduledExecutorService scheduler;
public CPUMonitor(int intervalSeconds, double alertThreshold) {
this.intervalSeconds = intervalSeconds;
this.alertThreshold = alertThreshold;
SystemInfo si = new SystemInfo();
HardwareAbstractionLayer hal = si.getHardware();
this.processor = hal.getProcessor();
}
public void start() {
scheduler = Executors.newSingleThreadScheduledExecutor();
scheduler.scheduleAtFixedRate(this::collectAndLog, 0, intervalSeconds, TimeUnit.SECONDS);
logger.info("✅ CPU 监控器已启动,间隔:{}秒,告警阈值:{}%", intervalSeconds, alertThreshold);
}
public void stop() {
if (scheduler != null && !scheduler.isShutdown()) {
scheduler.shutdown();
logger.info("⏹️ CPU 监控器已停止");
}
}
private void collectAndLog() {
double[] load = processor.getProcessorCpuLoadBetweenTicks();
double usage = load[0] * 100; // 获取整体 CPU 使用率
String timestamp = LocalDateTime.now().format(formatter);
String logMessage = String.format("[%s] CPU 使用率: %.2f%%", timestamp, usage);
if (usage > alertThreshold) {
logger.warn("🚨 高负载警告!{}", logMessage);
triggerAlert(usage);
} else {
logger.info(logMessage);
}
}
private void triggerAlert(double usage) {
// 此处可扩展:发送邮件、调用 webhook、写入数据库等
System.err.println("📢 触发告警逻辑:CPU 使用率 " + String.format("%.2f", usage) + "% 超过阈值 " + alertThreshold + "%");
}
public static void main(String[] args) {
int interval = args.length > 0 ? Integer.parseInt(args[0]) : 5;
double threshold = args.length > 1 ? Double.parseDouble(args[1]) : 80.0;
CPUMonitor monitor = new CPUMonitor(interval, threshold);
Runtime.getRuntime().addShutdownHook(new Thread(monitor::stop));
monitor.start();
// 保持主线程运行
try {
Thread.currentThread().join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
logger.error("主线程被中断", e);
}
}
}配置 Logback 输出到文件
创建 src/main/resources/logback.xml:
<configuration>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/cpu_monitor.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>logs/cpu_monitor.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>10MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>1GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="FILE"/>
<appender-ref ref="STDOUT"/>
</root>
</configuration>编译并运行:
mvn compile mvn exec:java -Dexec.mainClass="CPUMonitor" -Dexec.args="5 85"
Shell 与 Java 协同工作架构
在实际项目中,我们常采用“Shell + Java”混合架构:
- Shell 负责轻量级采集、快速响应、系统级操作
- Java 负责复杂逻辑、持久化、网络通信、Web 服务
下面是一个协同架构示意图:
渲染错误: Mermaid 渲染失败: Lexical error on line 2. Unrecognized text. ...|每5秒采集| B[/proc/stat] B --> C{CPU 使用 -----------------------^
这种架构的优势:
🔹 Shell 脚本资源占用极低,适合高频采集
🔹 Java 服务可横向扩展,处理复杂业务逻辑
🔹 解耦设计,便于独立部署与维护
数据持久化与可视化
采集到的数据若不存储和展示,价值将大打折扣。我们可以将数据写入时序数据库(如 InfluxDB),再通过 Grafana 展示仪表盘。
示例:Java 写入 InfluxDB
添加依赖:
<dependency>
<groupId>com.influxdb</groupId>
<artifactId>influxdb-client-java</artifactId>
<version>7.0.0</version>
</dependency>修改 triggerAlert 方法:
private InfluxDBClient influxDBClient;
private String bucket = "system_metrics";
private String org = "myorg";
public CPUMonitor(int intervalSeconds, double alertThreshold) {
// ... 原有代码
initInfluxDB();
}
private void initInfluxDB() {
influxDBClient = InfluxDBClientFactory.create(
"http://localhost:8086",
"your-token".toCharArray(),
org,
bucket
);
}
private void writeMetric(double usage) {
Point point = Point.measurement("cpu_usage")
.addTag("host", "server-01")
.addField("value", usage)
.time(Instant.now(), WritePrecision.NS);
influxDBClient.getWriteApiBlocking().writePoint(point);
logger.debug("📈 数据已写入 InfluxDB: {}%", usage);
}🔗 Grafana 官网:https://grafana.com/
🔗 InfluxDB 文档:https://docs.influxdata.com/influxdb/
高级功能:智能基线告警
固定阈值告警容易误报(如夜间低峰期 vs 白天高峰期)。更好的做法是建立“动态基线”。
我们可以:
- 记录历史同期数据(如过去7天同一时刻)
- 计算均值 + 标准差
- 当前值 > 均值 + 2×标准差 时触发告警
Java 实现伪代码:
public class BaselineAlertManager {
private Map<String, List<Double>> historyData = new ConcurrentHashMap<>();
public boolean shouldAlert(String timeKey, double currentValue) {
List<Double> pastValues = historyData.getOrDefault(timeKey, new ArrayList<>());
if (pastValues.size() < 5) {
pastValues.add(currentValue);
historyData.put(timeKey, pastValues);
return false; // 数据不足,暂不告警
}
double mean = pastValues.stream().mapToDouble(v -> v).average().orElse(0.0);
double stdDev = calculateStandardDeviation(pastValues, mean);
double baseline = mean + 2 * stdDev;
if (currentValue > baseline) {
logger.warn("📈 动态基线告警:当前 {}%,基线 {}%", currentValue, baseline);
return true;
}
// 更新历史数据(滑动窗口)
if (pastValues.size() >= 30) {
pastValues.remove(0);
}
pastValues.add(currentValue);
return false;
}
private double calculateStandardDeviation(List<Double> values, double mean) {
double sum = values.stream()
.mapToDouble(v -> Math.pow(v - mean, 2))
.sum();
return Math.sqrt(sum / values.size());
}
}性能测试与优化建议
任何监控系统自身不应成为性能负担。以下是优化要点:
Shell 脚本优化
- ✅ 使用
read直接解析/proc/stat,避免多次grep/awk - ✅ 减少不必要的子进程创建(如避免在循环内调用
date,改用变量缓存) - ✅ 使用
printf替代echo+ 命令替换提高精度
Java 应用优化
- ✅ 使用
ScheduledExecutorService而非Timer,更稳定 - ✅ 启用 JVM 参数优化:
-XX:+UseG1GC -Xmx128m - ✅ 批量写入数据库,而非每次采集都写
- ✅ 使用连接池管理数据库/HTTP 连接
移动端告警推送示例
当 CPU 异常时,可通过企业微信、钉钉或 Telegram 发送通知。
以企业微信为例(Java):
public class WeComNotifier {
private static final String WEBHOOK_URL = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY";
public static void sendAlert(String message) {
String jsonPayload = String.format("""
{
"msgtype": "text",
"text": {
"content": "%s",
"mentioned_list": ["@all"]
}
}
""", message);
try {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(WEBHOOK_URL))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(jsonPayload))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200) {
logger.info("✅ 企业微信消息发送成功");
} else {
logger.error("❌ 发送失败: {}", response.body());
}
} catch (Exception e) {
logger.error("发送企业微信消息异常", e);
}
}
}企业微信 API 文档:https://work.weixin.qq.com/api/doc
扩展功能:监控多核 CPU 与进程级占用
除了整体 CPU 使用率,有时还需监控:
- 各核心使用率分布(是否存在单核瓶颈?)
- 特定进程的 CPU 占用(如 Java 进程)
Shell 获取各核心使用率
#!/bin/bash
get_per_core_usage() {
cores=()
while IFS= read -r line; do
if [[ $line =~ ^cpu[0-9]+ ]]; then
cores+=("$line")
fi
done < /proc/stat
for core_line in "${cores[@]}"; do
read -r core_name user nice system idle iowait irq softirq steal <<< "$core_line"
total=$((user + nice + system + idle + iowait + irq + softirq + steal))
idle_time=$idle
# 存储初始值(略)...
# 之后计算每个核心的使用率(逻辑同整体 CPU)
# ...
done
}Java 获取指定进程 CPU 占用
OSHI 也支持按进程统计:
import oshi.software.os.OSProcess;
// ...
for (OSProcess p : si.getOperatingSystem().getProcesses(5, ProcessSort.CPU_DESC)) {
if (p.getName().contains("java")) {
double cpuUsage = p.getProcessCpuLoadCumulative() * 100;
logger.info("Java 进程 [{}] CPU 占用: {:.2f}%", p.getProcessID(), cpuUsage);
}
}生产环境部署建议
将监控脚本投入生产前,请考虑以下事项:
1. 权限最小化原则
- 创建专用用户
monitor运行脚本 - 限制脚本可访问的目录和命令
- 使用
sudo白名单控制特权操作
2. 高可用与自愈
- 使用
systemd管理服务,自动重启崩溃进程 - 配置健康检查端口,供外部探活
- 多节点部署,避免单点故障
3. 日志与审计
- 所有告警记录必须包含时间戳、主机名、指标值
- 定期归档旧日志,防止磁盘占满
- 敏感操作(如重启服务)需记录操作人
4. 容量规划
- 监控脚本自身 CPU/内存占用应 < 1%
- 数据库保留策略:热数据7天,冷数据90天
- 网络带宽预留:避免监控流量影响业务
开源监控工具对比
虽然我们实现了自研监控,但在大型系统中,使用成熟开源工具往往更高效:
| 工具 | 优势 | 适用场景 |
|---|---|---|
| Prometheus | 强大的查询语言,生态丰富 | 云原生、K8s 环境 |
| Zabbix | 企业级功能完整,支持自动发现 | 传统 IDC、混合云 |
| Telegraf | 轻量级采集器,插件丰富 | 边缘设备、IoT |
| Datadog | SaaS 服务,开箱即用 | 中小型团队快速上手 |
🔗 Prometheus 官网:https://prometheus.io/
🔗 Zabbix 官网:https://www.zabbix.com/
自研监控的价值在于:
- 深度定制业务逻辑
- 降低 License 成本
- 锻炼团队技术能力
结语:监控是持续演进的过程
CPU 监控只是系统可观测性的冰山一角。真正的稳定性保障需要结合:
🔸 日志监控(Log Monitoring)
🔸 指标监控(Metrics Monitoring)
🔸 链路追踪(Distributed Tracing)
🔸 健康检查(Health Check)
🔸 自动化修复(Auto Remediation)
希望本文提供的 Shell 脚本与 Java 代码能为你构建监控体系打下坚实基础。记住:没有完美的监控,只有不断迭代的监控。随着业务增长,你的监控系统也应随之进化。
最后思考题:如何让监控系统在 CPU 100% 时仍能正常发送告警?欢迎留言讨论!
附录:相关 Linux 命令速查
# 查看实时 CPU 使用率 top -bn1 | grep "Cpu(s)" # 查看每个核心使用率 mpstat -P ALL 1 # 查看进程 CPU 占用 TOP 10 ps aux --sort=-%cpu | head -11 # 查看系统平均负载 uptime # 查看 CPU 详细信息 lscpu
Happy Monitoring!
以上就是通过Shell脚本监控Linux系统的CPU使用率的详细内容,更多关于Linux Shell脚本监控CPU使用率的资料请关注脚本之家其它相关文章!
