|
@@ -0,0 +1,169 @@
|
|
|
|
|
+/// 磁盘空间监控
|
|
|
|
|
+const { exec } = require('child_process');
|
|
|
|
|
+const { promisify } = require('util');
|
|
|
|
|
+const execAsync = promisify(exec);
|
|
|
|
|
+
|
|
|
|
|
+const { sendEmail } = require('./email');
|
|
|
|
|
+const { sendSms } = require('./sms');
|
|
|
|
|
+const SmsTemplate = require('./sms-templates');
|
|
|
|
|
+
|
|
|
|
|
+// 阈值配置
|
|
|
|
|
+const USAGE_THRESHOLD = 90; // 使用率阈值 90%
|
|
|
|
|
+const FREE_SPACE_THRESHOLD = 10; // 剩余空间阈值 10G
|
|
|
|
|
+
|
|
|
|
|
+async function getDiskInfo() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const { stdout } = await execAsync('df -h /');
|
|
|
|
|
+ const lines = stdout.trim().split('\n');
|
|
|
|
|
+
|
|
|
|
|
+ if (lines.length < 2) {
|
|
|
|
|
+ throw new Error('无法解析df命令输出');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 解析df输出
|
|
|
|
|
+ const dataLine = lines[1].trim().split(/\s+/);
|
|
|
|
|
+ const [filesystem, size, used, avail, usePercent, mountpoint] = dataLine;
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ filesystem,
|
|
|
|
|
+ size,
|
|
|
|
|
+ used,
|
|
|
|
|
+ avail,
|
|
|
|
|
+ usePercent: parseInt(usePercent),
|
|
|
|
|
+ mountpoint,
|
|
|
|
|
+ raw: stdout
|
|
|
|
|
+ };
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ throw new Error(`获取磁盘信息失败: ${error.message}`);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function parseSize(sizeStr) {
|
|
|
|
|
+ // 将 "106G", "1.5T" 等格式转换为GB数值
|
|
|
|
|
+ const match = sizeStr.match(/^([\d.]+)([KMGT]?)$/i);
|
|
|
|
|
+ if (!match) return 0;
|
|
|
|
|
+
|
|
|
|
|
+ const value = parseFloat(match[1]);
|
|
|
|
|
+ const unit = match[2].toUpperCase();
|
|
|
|
|
+
|
|
|
|
|
+ const multipliers = { '': 1/1024, 'K': 1/1024/1024, 'M': 1/1024, 'G': 1, 'T': 1024 };
|
|
|
|
|
+ return value * (multipliers[unit] || 1);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function checkDiskSpace() {
|
|
|
|
|
+ let diskInfo;
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ diskInfo = await getDiskInfo();
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ console.error('磁盘信息获取失败:', e.message);
|
|
|
|
|
+ return {
|
|
|
|
|
+ result: false,
|
|
|
|
|
+ errcode: 200,
|
|
|
|
|
+ title: '磁盘监控:无法获取磁盘信息',
|
|
|
|
|
+ sms: SmsTemplate.SMS_DISK_ERROR,
|
|
|
|
|
+ data: [e.message],
|
|
|
|
|
+ diskInfo: null
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const usagePercent = diskInfo.usePercent;
|
|
|
|
|
+ const availGB = parseSize(diskInfo.avail);
|
|
|
|
|
+
|
|
|
|
|
+ console.log(`磁盘使用率: ${usagePercent}%, 剩余空间: ${availGB.toFixed(2)}GB`);
|
|
|
|
|
+
|
|
|
|
|
+ // 检查是否超过阈值
|
|
|
|
|
+ if (usagePercent >= USAGE_THRESHOLD || availGB < FREE_SPACE_THRESHOLD) {
|
|
|
|
|
+ const reasons = [];
|
|
|
|
|
+ if (usagePercent >= USAGE_THRESHOLD) {
|
|
|
|
|
+ reasons.push(`使用率达到 ${usagePercent}% (阈值: ${USAGE_THRESHOLD}%)`);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (availGB < FREE_SPACE_THRESHOLD) {
|
|
|
|
|
+ reasons.push(`剩余空间仅 ${availGB.toFixed(2)}GB (阈值: ${FREE_SPACE_THRESHOLD}GB)`);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ result: false,
|
|
|
|
|
+ errcode: 201,
|
|
|
|
|
+ title: '磁盘空间告警',
|
|
|
|
|
+ sms: SmsTemplate.SMS_DISK_WARNING,
|
|
|
|
|
+ data: [
|
|
|
|
|
+ `文件系统: ${diskInfo.filesystem}`,
|
|
|
|
|
+ `挂载点: ${diskInfo.mountpoint}`,
|
|
|
|
|
+ `总容量: ${diskInfo.size}`,
|
|
|
|
|
+ `已使用: ${diskInfo.used}`,
|
|
|
|
|
+ `可用空间: ${diskInfo.avail}`,
|
|
|
|
|
+ `使用率: ${usagePercent}%`,
|
|
|
|
|
+ '',
|
|
|
|
|
+ '告警原因:',
|
|
|
|
|
+ ...reasons,
|
|
|
|
|
+ '',
|
|
|
|
|
+ '详细信息:',
|
|
|
|
|
+ diskInfo.raw
|
|
|
|
|
+ ],
|
|
|
|
|
+ diskInfo
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ result: true,
|
|
|
|
|
+ errcode: 0,
|
|
|
|
|
+ title: '磁盘空间正常',
|
|
|
|
|
+ sms: SmsTemplate.SMS_RESUME,
|
|
|
|
|
+ data: [
|
|
|
|
|
+ `文件系统: ${diskInfo.filesystem}`,
|
|
|
|
|
+ `使用率: ${usagePercent}%`,
|
|
|
|
|
+ `剩余空间: ${diskInfo.avail}`
|
|
|
|
|
+ ],
|
|
|
|
|
+ diskInfo
|
|
|
|
|
+ };
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function run(lastErrCode = 0) {
|
|
|
|
|
+ console.log("检查磁盘空间...", new Date());
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ let result = null;
|
|
|
|
|
+
|
|
|
|
|
+ // 连续检测3次才发送通知,减少误报
|
|
|
|
|
+ for (let i = 0; i < 3; i++) {
|
|
|
|
|
+ result = await checkDiskSpace();
|
|
|
|
|
+ console.log(result);
|
|
|
|
|
+
|
|
|
|
|
+ if (!result.result) {
|
|
|
|
|
+ console.warn("磁盘空间检查出现异常,一分钟后重试");
|
|
|
|
|
+ await delay(60 * 1000);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ console.log("磁盘空间正常");
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!result.result) {
|
|
|
|
|
+ console.error("连续3次检查磁盘空间异常,发送通知");
|
|
|
|
|
+ sendEmail(result.title, result.data);
|
|
|
|
|
+ sendSms(result.sms);
|
|
|
|
|
+ console.log("5分钟后再试");
|
|
|
|
|
+ setTimeout(run, 5 * 60 * 1000, result.errcode);
|
|
|
|
|
+ return;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 如果上次是出错的情况,那么属于服务恢复,发送恢复通知
|
|
|
|
|
+ if (lastErrCode === 200 || lastErrCode === 201) {
|
|
|
|
|
+ sendEmail(result.title, result.data);
|
|
|
|
|
+ sendSms(result.sms);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ console.error(err.stack);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function delay(ms) {
|
|
|
|
|
+ return new Promise(done => setTimeout(done, ms));
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+module.exports = { run, checkDiskSpace };
|
|
|
|
|
+
|
|
|
|
|
+if (require.main == module) {
|
|
|
|
|
+ run();
|
|
|
|
|
+}
|