本次利用腾讯云代码助手 CodeBuddy 纯自然语言手搓一个服务器磁盘空间监控系统的代码如下,可以开箱即用。

https://github.com/aimdotsh/autodiy/tree/main

一直想开发一个服务器的磁盘监控系统,并能够通过 web 页面进行磁盘空间使用率的展示,无奈技不如人,不懂开发,最近在使用了 腾讯云代码助手 CodeBuddy 的 Craft,发现只要有想法,基本上就能实现,不管把困扰我多年的跑步页面展示给完善了,没想到半天的时间不到就帮我完成了服务器的磁盘监控/展示系统。

关于 腾讯云代码助手 CodeBuddy 的介绍就不多说了,具体见官方网站:https://copilot.tencent.com,也可以参加我的 Workouts Page 借着 AI 的东风进行了改版

本次的需求刚开始是这样的

通过 shell 脚本写一个数据库磁盘空间监控脚本,并生成每天的磁盘空间增长的曲线统计,将df -hP 存到 sqlite 库的的表 dfinfo表中,并通过页面信息读取 sqlite 表的数据进行每天的磁盘空间增长的一个增长曲线图

进入 CodeBuddy 的 Craft,输入你的想法

image-20250530151901429

一气呵成,不光有代码,还有 使用说明

image-20250530152008560

说时候,第一次就启动完成,我是惊呆了的。我本地是mac系统,直接启动 ./start_monitor.sh

不光把磁盘空间使用率插入到 sqlitedb ,还启动了web服务,在这之前我一直认为,web服务需要 apache或者nginx来提供,没想到一个 py脚本就可以。

image-20250530152147696

浏览器打开:http://localhost:8000/ 果然很可以惊艳。

image-20250530152340319

看到这个里,这个磁盘监控只采集了当前主机的,我想采集其他主机的磁盘信息,于是又通过自然语言把需求提交给Craft :

我想把多台服务器的磁盘信息定时采集并通过web展示,可以在web选择服务器进行磁盘使用率的查看

image-20250530152558190 image-20250530152656724

具体修改如下:

这个任务是对之前创建的磁盘监控系统的扩展,现在需要支持多台服务器的磁盘信息采集和展示。我需要修改现有的代码来实现这个功能。

主要需要修改的部分包括:

  1. 数据库结构:需要添加服务器标识字段
  2. 数据收集脚本:需要支持指定服务器名称
  3. Web服务器:需要提供服务器列表API和按服务器筛选的功能
  4. 前端页面:需要添加服务器选择下拉框

让我们一步步实现这些修改。首先,我需要查看现有的文件结构,确认我们已经创建的文件。

list_files disk_monitor

我们需要修改以下内容:

  1. 首先修改数据收集脚本,添加服务器标识支持
  2. 更新数据库结构,添加服务器标识字段
  3. 修改Web服务器API,支持服务器列表和按服务器筛选
  4. 更新前端页面,添加服务器选择功能

让我们先修改数据收集脚本,使其支持指定服务器名称。

write_to_file disk_monitor/collect_disk_info.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
>#!/bin/bash

># 检查参数
>if [ $# -ne 1 ]; then
echo "使用方法: $0 <服务器名称>"
exit 1
>fi

>SERVER_NAME="$1"

># 设置数据库文件路径
>DB_FILE="$(dirname "$0")/disk_data.db"
>DATE=$(date +"%Y-%m-%d %H:%M:%S")

># 检查SQLite3是否安装
>if ! command -v sqlite3 &> /dev/null; then
echo "错误: 未找到sqlite3命令。请安装SQLite3。"
exit 1
>fi

># 如果数据库不存在,创建数据库和表
>if [ ! -f "$DB_FILE" ]; then
echo "创建新的数据库文件: $DB_FILE"
sqlite3 "$DB_FILE" <<EOF
>CREATE TABLE dfinfo (
id INTEGER PRIMARY KEY AUTOINCREMENT,
server_name TEXT NOT NULL,
timestamp TEXT NOT NULL,
filesystem TEXT NOT NULL,
size TEXT NOT NULL,
used TEXT NOT NULL,
available TEXT NOT NULL,
use_percent INTEGER NOT NULL,
mounted_on TEXT NOT NULL
>);

>CREATE INDEX idx_server_time ON dfinfo(server_name, timestamp);
>EOF
if [ $? -ne 0 ]; then
echo "错误: 创建数据库失败。"
exit 1
fi
>else
# 检查是否需要更新表结构(添加server_name列)
if ! sqlite3 "$DB_FILE" ".schema dfinfo" | grep -q "server_name"; then
echo "更新数据库结构..."
sqlite3 "$DB_FILE" <<EOF
>ALTER TABLE dfinfo ADD COLUMN server_name TEXT NOT NULL DEFAULT 'localhost';
>CREATE INDEX idx_server_time ON dfinfo(server_name, timestamp);
>EOF
fi
>fi

># 获取df命令输出并跳过第一行(标题行)
>df_output=$(df -hP | tail -n +2)

># 遍历每一行df输出并插入到数据库
>echo "$df_output" | while read -r line; do
# 解析df输出
filesystem=$(echo "$line" | awk '{print $1}')
size=$(echo "$line" | awk '{print $2}')
used=$(echo "$line" | awk '{print $3}')
available=$(echo "$line" | awk '{print $4}')
use_percent=$(echo "$line" | awk '{print $5}' | tr -d '%')
mounted_on=$(echo "$line" | awk '{print $6}')

# 插入数据到SQLite数据库
sqlite3 "$DB_FILE" <<EOF
>INSERT INTO dfinfo (server_name, timestamp, filesystem, size, used, available, use_percent, mounted_on)
>VALUES ('$SERVER_NAME', '$DATE', '$filesystem', '$size', '$used', '$available', $use_percent, '$mounted_on');
>EOF

if [ $? -ne 0 ]; then
echo "错误: 插入数据失败: $filesystem"
else
echo "成功插入数据: $SERVER_NAME - $filesystem ($use_percent%)"
fi
>done

>echo "数据收集完成: $SERVER_NAME - $DATE"

现在我们需要修改Python服务器脚本,使其支持多服务器的数据查询和展示。主要需要添加:

  1. 获取服务器列表的API
  2. 修改现有API,支持按服务器筛选数据

让我们更新服务器脚本。

write_to_file disk_monitor/disk_monitor_server.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
>#!/usr/bin/env python3
># -*- coding: utf-8 -*-

>import os
>import sqlite3
>import json
>from datetime import datetime, timedelta
>from http.server import HTTPServer, SimpleHTTPRequestHandler
>import urllib.parse

># 数据库文件路径
>DB_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "disk_data.db")

>class DiskMonitorHandler(SimpleHTTPRequestHandler):
# 设置静态文件目录
def __init__(self, *args, **kwargs):
self.static_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "static")
super().__init__(*args, **kwargs)

def do_GET(self):
parsed_path = urllib.parse.urlparse(self.path)

# API路由处理
if parsed_path.path.startswith('/api/'):
self.handle_api(parsed_path.path)
else:
# 静态文件处理
if self.path == '/':
self.path = '/index.html'

file_path = os.path.join(self.static_dir, self.path.lstrip('/'))

if os.path.exists(file_path) and os.path.isfile(file_path):
self.send_static_file(file_path)
else:
self.send_error(404, "File not found")

def send_static_file(self, file_path):
try:
with open(file_path, 'rb') as f:
content = f.read()

self.send_response(200)

# 设置正确的Content-Type
if file_path.endswith('.html'):
self.send_header('Content-Type', 'text/html; charset=utf-8')
elif file_path.endswith('.js'):
self.send_header('Content-Type', 'application/javascript; charset=utf-8')
elif file_path.endswith('.css'):
self.send_header('Content-Type', 'text/css; charset=utf-8')
else:
self.send_header('Content-Type', 'application/octet-stream')

self.send_header('Content-Length', str(len(content)))
self.end_headers()
self.wfile.write(content)
except Exception as e:
self.send_error(500, str(e))

def handle_api(self, path):
if path == '/api/disk_usage':
self.get_disk_usage()
elif path == '/api/filesystems':
self.get_filesystems()
elif path == '/api/servers':
self.get_servers()
else:
self.send_error(404, "API endpoint not found")

def get_disk_usage(self):
try:
# 获取查询参数
query_components = urllib.parse.parse_qs(urllib.parse.urlparse(self.path).query)
days = int(query_components.get('days', ['7'])[0])
filesystem = query_components.get('filesystem', ['all'])[0]
server = query_components.get('server', ['all'])[0]

# 计算日期范围
end_date = datetime.now()
start_date = end_date - timedelta(days=days)

# 连接数据库
conn = sqlite3.connect(DB_FILE)
conn.row_factory = sqlite3.Row
cursor = conn.cursor()

# 构建查询
params = [start_date.strftime('%Y-%m-%d')]

if server == 'all' and filesystem == 'all':
query = """
SELECT date(timestamp) as date,
server_name,
filesystem,
avg(use_percent) as use_percent,
max(size) as size,
max(used) as used,
max(available) as available
FROM dfinfo
WHERE timestamp >= ?
GROUP BY date(timestamp), server_name, filesystem
ORDER BY date(timestamp), server_name, filesystem
"""
elif server == 'all':
query = """
SELECT date(timestamp) as date,
server_name,
filesystem,
avg(use_percent) as use_percent,
max(size) as size,
max(used) as used,
max(available) as available
FROM dfinfo
WHERE timestamp >= ? AND filesystem = ?
GROUP BY date(timestamp), server_name, filesystem
ORDER BY date(timestamp), server_name
"""
params.append(filesystem)
elif filesystem == 'all':
query = """
SELECT date(timestamp) as date,
server_name,
filesystem,
avg(use_percent) as use_percent,
max(size) as size,
max(used) as used,
max(available) as available
FROM dfinfo
WHERE timestamp >= ? AND server_name = ?
GROUP BY date(timestamp), filesystem
ORDER BY date(timestamp), filesystem
"""
params.append(server)
else:
query = """
SELECT date(timestamp) as date,
server_name,
filesystem,
avg(use_percent) as use_percent,
max(size) as size,
max(used) as used,
max(available) as available
FROM dfinfo
WHERE timestamp >= ? AND server_name = ? AND filesystem = ?
GROUP BY date(timestamp)
ORDER BY date(timestamp)
"""
params.extend([server, filesystem])

# 执行查询
cursor.execute(query, params)

# 获取结果
rows = cursor.fetchall()

# 转换为JSON格式
result = []
for row in rows:
result.append({
'date': row['date'],
'server_name': row['server_name'],
'filesystem': row['filesystem'],
'use_percent': row['use_percent'],
'size': row['size'],
'used': row['used'],
'available': row['available']
})

conn.close()

# 发送响应
self.send_response(200)
self.send_header('Content-Type', 'application/json; charset=utf-8')
self.end_headers()
self.wfile.write(json.dumps(result).encode('utf-8'))

except Exception as e:
self.send_error(500, str(e))

def get_filesystems(self):
try:
# 获取查询参数
query_components = urllib.parse.parse_qs(urllib.parse.urlparse(self.path).query)
server = query_components.get('server', ['all'])[0]

# 连接数据库
conn = sqlite3.connect(DB_FILE)
conn.row_factory = sqlite3.Row
cursor = conn.cursor()

# 查询文件系统
if server == 'all':
query = """
SELECT DISTINCT server_name, filesystem, mounted_on
FROM dfinfo
ORDER BY server_name, filesystem
"""
cursor.execute(query)
else:
query = """
SELECT DISTINCT server_name, filesystem, mounted_on
FROM dfinfo
WHERE server_name = ?
ORDER BY filesystem
"""
cursor.execute(query, (server,))

# 获取结果
rows = cursor.fetchall()

# 转换为JSON格式
result = []
for row in rows:
result.append({
'server_name': row['server_name'],
'filesystem': row['filesystem'],
'mounted_on': row['mounted_on']
})

conn.close()

# 发送响应
self.send_response(200)
self.send_header('Content-Type', 'application/json; charset=utf-8')
self.end_headers()
self.wfile.write(json.dumps(result).encode('utf-8'))

except Exception as e:
self.send_error(500, str(e))

def get_servers(self):
try:
# 连接数据库
conn = sqlite3.connect(DB_FILE)
conn.row_factory = sqlite3.Row
cursor = conn.cursor()

# 查询所有服务器
query = """
SELECT DISTINCT server_name
FROM dfinfo
ORDER BY server_name
"""
cursor.execute(query)

# 获取结果
rows = cursor.fetchall()

# 转换为JSON格式
result = []
for row in rows:
result.append(row['server_name'])

conn.close()

# 发送响应
self.send_response(200)
self.send_header('Content-Type', 'application/json; charset=utf-8')
self.end_headers()
self.wfile.write(json.dumps(result).encode('utf-8'))

except Exception as e:
self.send_error(500, str(e))

>def run_server(port=8000):
server_address = ('', port)
httpd = HTTPServer(server_address, DiskMonitorHandler)
print(f"启动服务器在 http://localhost:{port}")
httpd.serve_forever()

>if __name__ == "__main__":
run_server()

现在我们需要更新前端页面,添加服务器选择功能,并修改图表显示逻辑以支持多服务器数据的展示。主要更改包括:

  1. 添加服务器选择下拉框
  2. 修改文件系统选择逻辑,根据选择的服务器更新
  3. 更新图表显示逻辑,支持多服务器数据的展示
  4. 优化图表标题和图例显示

write_to_file disk_monitor/static/index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
><!DOCTYPE html>
><html lang="zh-CN">
><head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>多服务器磁盘空间监控</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: #f5f5f5;
}
.container {
max-width: 1200px;
margin: 0 auto;
background-color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.header {
margin-bottom: 20px;
}
.controls {
margin-bottom: 20px;
display: flex;
gap: 20px;
align-items: center;
flex-wrap: wrap;
}
select, button {
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
min-width: 200px;
}
button {
background-color: #4CAF50;
color: white;
border: none;
cursor: pointer;
min-width: auto;
}
button:hover {
background-color: #45a049;
}
.chart-container {
position: relative;
height: 400px;
margin-bottom: 20px;
}
.stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin-top: 20px;
}
.stat-card {
background-color: #f8f9fa;
padding: 15px;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.stat-card h3 {
margin: 0 0 10px 0;
color: #333;
}
.stat-value {
font-size: 24px;
font-weight: bold;
color: #2196F3;
}
.server-info {
margin-bottom: 10px;
font-weight: bold;
color: #333;
}
</style>
></head>
><body>
<div class="container">
<div class="header">
<h1>多服务器磁盘空间监控</h1>
</div>

<div class="controls">
<select id="server-select">
<option value="all">所有服务器</option>
</select>

<select id="filesystem-select">
<option value="all">所有文件系统</option>
</select>

<select id="time-range">
<option value="7">最近7天</option>
<option value="14">最近14天</option>
<option value="30">最近30天</option>
</select>

<button onclick="updateChart()">更新图表</button>
</div>

<div class="server-info" id="server-info"></div>

<div class="chart-container">
<canvas id="diskChart"></canvas>
</div>

<div class="stats">
<div class="stat-card">
<h3>当前使用率</h3>
<div id="current-usage" class="stat-value">-</div>
</div>
<div class="stat-card">
<h3>总容量</h3>
<div id="total-size" class="stat-value">-</div>
</div>
<div class="stat-card">
<h3>已使用</h3>
<div id="used-space" class="stat-value">-</div>
</div>
<div class="stat-card">
<h3>可用空间</h3>
<div id="available-space" class="stat-value">-</div>
</div>
</div>
</div>

<script>
let diskChart = null;
const colors = [
'#2196F3', '#4CAF50', '#FFC107', '#E91E63', '#9C27B0',
'#00BCD4', '#FF5722', '#795548', '#607D8B', '#3F51B5'
];
let colorIndex = 0;

// 获取随机颜色
function getNextColor() {
const color = colors[colorIndex];
colorIndex = (colorIndex + 1) % colors.length;
return color;
}

// 初始化图表
function initChart() {
const ctx = document.getElementById('diskChart').getContext('2d');
diskChart = new Chart(ctx, {
type: 'line',
data: {
labels: [],
datasets: []
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
title: {
display: true,
text: '磁盘使用率趋势'
},
tooltip: {
mode: 'index',
intersect: false
}
},
scales: {
y: {
beginAtZero: true,
max: 100,
title: {
display: true,
text: '使用率 (%)'
}
},
x: {
title: {
display: true,
text: '日期'
}
}
}
}
});
}

// 更新图表数据
async function updateChart() {
const server = document.getElementById('server-select').value;
const filesystem = document.getElementById('filesystem-select').value;
const days = document.getElementById('time-range').value;

try {
const response = await fetch(`/api/disk_usage?server=${server}&filesystem=${filesystem}&days=${days}`);
const data = await response.json();

// 处理数据
const datasets = {};
const labels = new Set();

data.forEach(item => {
labels.add(item.date);
const key = server === 'all' ?
`${item.server_name} - ${item.filesystem}` :
item.filesystem;

if (!datasets[key]) {
datasets[key] = {
label: key,
data: {},
borderColor: getNextColor(),
fill: false,
tension: 0.1
};
}
datasets[key].data[item.date] = item.use_percent;

// 更新统计信息(使用最新数据)
if (item.date === Array.from(labels).sort().pop()) {
updateStats(item);
}
});

// 更新服务器信息显示
updateServerInfo(server, filesystem);

// 转换数据为Chart.js格式
const sortedLabels = Array.from(labels).sort();
const chartData = Object.values(datasets).map(dataset => ({
...dataset,
data: sortedLabels.map(label => dataset.data[label] || null)
}));

// 更新图表
diskChart.data.labels = sortedLabels;
diskChart.data.datasets = chartData;
diskChart.update();

} catch (error) {
console.error('获取数据失败:', error);
alert('获取数据失败,请检查服务器状态。');
}
}

// 更新服务器信息显示
function updateServerInfo(server, filesystem) {
const serverInfo = document.getElementById('server-info');
if (server === 'all' && filesystem === 'all') {
serverInfo.textContent = '显示所有服务器的所有文件系统';
} else if (server === 'all') {
serverInfo.textContent = `显示所有服务器的 ${filesystem} 文件系统`;
} else if (filesystem === 'all') {
serverInfo.textContent = `显示服务器 ${server} 的所有文件系统`;
} else {
serverInfo.textContent = `显示服务器 ${server}${filesystem} 文件系统`;
}
}

// 更新统计信息
function updateStats(data) {
document.getElementById('current-usage').textContent = `${data.use_percent.toFixed(1)}%`;
document.getElementById('total-size').textContent = data.size;
document.getElementById('used-space').textContent = data.used;
document.getElementById('available-space').textContent = data.available;
}

// 加载服务器列表
async function loadServers() {
try {
const response = await fetch('/api/servers');
const data = await response.json();

const select = document.getElementById('server-select');
data.forEach(server => {
const option = document.createElement('option');
option.value = server;
option.textContent = server;
select.appendChild(option);
});
} catch (error) {
console.error('加载服务器列表失败:', error);
}
}

// 加载文件系统列表
async function loadFilesystems() {
try {
const server = document.getElementById('server-select').value;
const response = await fetch(`/api/filesystems?server=${server}`);
const data = await response.json();

const select = document.getElementById('filesystem-select');
// 清除现有选项(保留"所有文件系统"选项)
select.innerHTML = '<option value="all">所有文件系统</option>';

data.forEach(fs => {
const option = document.createElement('option');
option.value = fs.filesystem;
option.textContent = `${fs.filesystem} (${fs.mounted_on})`;
select.appendChild(option);
});
} catch (error) {
console.error('加载文件系统列表失败:', error);
}
}

// 当服务器选择改变时更新文件系统列表
document.getElementById('server-select').addEventListener('change', () => {
loadFilesystems();
updateChart();
});

// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', () => {
initChart();
loadServers();
loadFilesystems();
updateChart();
});
</script>
></body>
></html>

现在我们需要更新启动脚本,使其支持多服务器的数据收集。我们需要:

  1. 修改启动脚本,支持指定服务器名称
  2. 添加服务器配置文件的支持
  3. 为每个服务器设置单独的定时任务

让我们创建一个服务器配置文件,然后修改启动脚本。

write_to_file disk_monitor/servers.conf

1
2
3
4
5
6
7
8
9
10
># 服务器配置文件
># 格式:服务器名称=主机地址
># 本地服务器示例:
>localhost=127.0.0.1

># 远程服务器示例(需要配置SSH免密登录):
># server1=192.168.1.101
># server2=192.168.1.102
># web-server=web.example.com
># db-server=db.example.com

现在我们需要创建一个远程数据收集脚本,用于从远程服务器收集磁盘信息。这个脚本将通过SSH连接到远程服务器,执行df命令,然后将结果保存到本地数据库。

write_to_file disk_monitor/collect_remote_disk_info.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
>#!/bin/bash

># 检查参数
>if [ $# -ne 2 ]; then
echo "使用方法: $0 <服务器名称> <服务器地址>"
exit 1
>fi

>SERVER_NAME="$1"
>SERVER_HOST="$2"

># 设置数据库文件路径
>SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
>DB_FILE="$SCRIPT_DIR/disk_data.db"
>DATE=$(date +"%Y-%m-%d %H:%M:%S")

># 检查SQLite3是否安装
>if ! command -v sqlite3 &> /dev/null; then
echo "错误: 未找到sqlite3命令。请安装SQLite3。"
exit 1
>fi

># 检查SSH连接
>if ! ssh -q -o BatchMode=yes -o ConnectTimeout=5 "$SERVER_HOST" exit; then
echo "错误: 无法连接到服务器 $SERVER_HOST"
exit 1
>fi

># 从远程服务器获取df命令输出
>echo "从服务器 $SERVER_NAME ($SERVER_HOST) 收集数据..."
>df_output=$(ssh "$SERVER_HOST" "df -hP" | tail -n +2)

>if [ -z "$df_output" ]; then
echo "错误: 从服务器 $SERVER_HOST 获取数据失败"
exit 1
>fi

># 遍历每一行df输出并插入到数据库
>echo "$df_output" | while read -r line; do
# 解析df输出
filesystem=$(echo "$line" | awk '{print $1}')
size=$(echo "$line" | awk '{print $2}')
used=$(echo "$line" | awk '{print $3}')
available=$(echo "$line" | awk '{print $4}')
use_percent=$(echo "$line" | awk '{print $5}' | tr -d '%')
mounted_on=$(echo "$line" | awk '{print $6}')

# 插入数据到SQLite数据库
sqlite3 "$DB_FILE" <<EOF
>INSERT INTO dfinfo (server_name, timestamp, filesystem, size, used, available, use_percent, mounted_on)
>VALUES ('$SERVER_NAME', '$DATE', '$filesystem', '$size', '$used', '$available', $use_percent, '$mounted_on');
>EOF

if [ $? -ne 0 ]; then
echo "错误: 插入数据失败: $filesystem"
else
echo "成功插入数据: $SERVER_NAME - $filesystem ($use_percent%)"
fi
>done

>echo "数据收集完成: $SERVER_NAME - $DATE"

现在我们需要更新启动脚本,使其能够:

  1. 读取服务器配置文件
  2. 为每个服务器设置定时任务
  3. 支持本地和远程服务器的数据收集
  4. 启动Web服务器

让我们修改启动脚本。

write_to_file disk_monitor/start_monitor.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
>#!/bin/bash

># 获取脚本所在目录的绝对路径
>SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
>COLLECT_LOCAL_SCRIPT="$SCRIPT_DIR/collect_disk_info.sh"
>COLLECT_REMOTE_SCRIPT="$SCRIPT_DIR/collect_remote_disk_info.sh"
>SERVER_SCRIPT="$SCRIPT_DIR/disk_monitor_server.py"
>CONFIG_FILE="$SCRIPT_DIR/servers.conf"
>DB_FILE="$SCRIPT_DIR/disk_data.db"

># 检查必要的脚本文件
>for script in "$COLLECT_LOCAL_SCRIPT" "$COLLECT_REMOTE_SCRIPT" "$SERVER_SCRIPT"; do
if [ ! -f "$script" ]; then
echo "错误: 找不到必要的脚本文件: $script"
exit 1
fi
>done

># 检查配置文件
>if [ ! -f "$CONFIG_FILE" ]; then
echo "错误: 找不到服务器配置文件: $CONFIG_FILE"
exit 1
>fi

># 确保脚本有执行权限
>chmod +x "$COLLECT_LOCAL_SCRIPT" "$COLLECT_REMOTE_SCRIPT" "$SERVER_SCRIPT"

># 创建临时文件存储cron任务
>TEMP_CRON=$(mktemp)
>crontab -l > "$TEMP_CRON" 2>/dev/null

># 清除旧的监控任务
>sed -i.bak "/collect_disk_info.sh/d" "$TEMP_CRON"
>sed -i.bak "/collect_remote_disk_info.sh/d" "$TEMP_CRON"

># 读取并处理服务器配置
>echo "配置服务器监控..."
>while IFS='=' read -r server_name server_host || [ -n "$server_name" ]; do
# 跳过注释和空行
[[ $server_name =~ ^#.*$ ]] && continue
[ -z "$server_name" ] && continue

# 去除可能的空格
server_name=$(echo "$server_name" | tr -d ' ')
server_host=$(echo "$server_host" | tr -d ' ')

if [ -z "$server_name" ] || [ -z "$server_host" ]; then
continue
fi

echo "设置服务器 $server_name ($server_host) 的监控..."

# 根据是否为本地主机选择不同的收集脚本
if [[ "$server_host" == "127.0.0.1" ]] || [[ "$server_host" == "localhost" ]]; then
# 本地服务器
echo "0 * * * * $COLLECT_LOCAL_SCRIPT \"$server_name\"" >> "$TEMP_CRON"
# 立即执行一次数据收集
"$COLLECT_LOCAL_SCRIPT" "$server_name"
else
# 远程服务器
echo "0 * * * * $COLLECT_REMOTE_SCRIPT \"$server_name\" \"$server_host\"" >> "$TEMP_CRON"
# 立即执行一次数据收集
"$COLLECT_REMOTE_SCRIPT" "$server_name" "$server_host"
fi

if [ $? -eq 0 ]; then
echo "成功设置服务器 $server_name 的监控"
else
echo "警告: 服务器 $server_name 的初始数据收集失败"
fi
>done < "$CONFIG_FILE"

># 安装新的cron任务
>crontab "$TEMP_CRON"
>rm "$TEMP_CRON"

>if [ $? -eq 0 ]; then
echo "定时任务设置成功,每小时执行一次数据收集。"
>else
echo "警告: 定时任务设置失败。"
>fi

># 显示当前的cron任务
>echo "当前配置的定时任务:"
>crontab -l | grep -E "collect_disk_info.sh|collect_remote_disk_info.sh"

># 启动Web服务器
>echo "启动Web服务器..."
>echo "访问 http://localhost:8000 查看磁盘监控页面"
>echo "按 Ctrl+C 停止服务器"

># 检查Python3是否安装
>if ! command -v python3 &> /dev/null; then
echo "错误: 未找到python3。请安装Python 3。"
exit 1
>fi

># 启动Web服务器
>"$SERVER_SCRIPT"

现在我们需要更新README.md文件,提供关于多服务器监控系统的使用说明。我们需要包括:

  1. 系统概述
  2. 安装和配置说明
  3. 如何添加远程服务器
  4. 如何设置SSH免密登录
  5. 使用方法
  6. 故障排除指南

write_to_file disk_monitor/README.md

多服务器磁盘空间监控系统

这是一个简单而强大的多服务器磁盘空间监控系统,可以定时收集多台服务器的磁盘使用情况,并通过Web界面直观地展示磁盘使用趋势。

功能特点

  • 支持监控多台服务器的磁盘使用情况
  • 自动定时收集数据(默认每小时一次)
  • 数据持久化存储到SQLite数据库
  • 通过Web界面可视化展示磁盘使用趋势
  • 支持按服务器和文件系统筛选数据
  • 支持选择不同的时间范围(7天、14天、30天)
  • 显示关键统计信息(使用率、总容量、已用空间、可用空间)

系统要求

  • Bash shell
  • Python 3
  • SQLite3
  • SSH客户端(用于远程服务器监控)

安装和配置

  1. 克隆或下载本项目到本地目录

  2. 确保所有脚本有执行权限:

    1
    chmod +x disk_monitor/*.sh disk_monitor/*.py
  3. 配置服务器列表:
    编辑 disk_monitor/servers.conf 文件,添加要监控的服务器:

    1
    2
    3
    4
    # 格式:服务器名称=主机地址
    localhost=127.0.0.1
    web-server=192.168.1.101
    db-server=db.example.com
  4. 对于远程服务器,设置SSH免密登录(见下文)

设置SSH免密登录

对于远程服务器监控,需要设置SSH免密登录,以便脚本可以自动连接到远程服务器:

  1. 生成SSH密钥对(如果尚未生成):

    1
    ssh-keygen -t rsa -b 4096
  2. 将公钥复制到远程服务器:

    1
    ssh-copy-id username@remote-server

    或手动将 ~/.ssh/id_rsa.pub 的内容添加到远程服务器的 ~/.ssh/authorized_keys 文件中

  3. 测试SSH连接:

    1
    ssh remote-server "echo 连接成功"

使用方法

  1. 启动监控系统:

    1
    2
    cd disk_monitor
    ./start_monitor.sh
  2. 打开Web浏览器,访问 http://localhost:8000

  3. 在Web界面上:

  • 从下拉菜单选择要查看的服务器
  • 选择特定的文件系统或查看所有文件系统
  • 选择时间范围(7天、14天或30天)
  • 点击”更新图表”按钮刷新数据

自定义配置

修改数据收集频率

默认情况下,系统每小时收集一次数据。要修改收集频率,请编辑 start_monitor.sh 文件中的cron表达式:

1
2
3
4
5
6
7
8
># 默认:每小时收集一次(0分时)
>echo "0 * * * * $COLLECT_LOCAL_SCRIPT \"$server_name\"" >> "$TEMP_CRON"

># 示例:每30分钟收集一次
>echo "*/30 * * * * $COLLECT_LOCAL_SCRIPT \"$server_name\"" >> "$TEMP_CRON"

># 示例:每天收集3次(每8小时一次)
>echo "0 */8 * * * $COLLECT_LOCAL_SCRIPT \"$server_name\"" >> "$TEMP_CRON"

修改Web服务器端口

默认情况下,Web服务器在端口8000上运行。要修改端口,请编辑 disk_monitor_server.py 文件末尾的 run_server() 函数调用:

1
2
>if __name__ == "__main__":
run_server(port=8080) # 修改为所需端口

数据库结构

系统使用SQLite数据库存储磁盘使用数据。数据库结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
>CREATE TABLE dfinfo (
id INTEGER PRIMARY KEY AUTOINCREMENT,
server_name TEXT NOT NULL,
timestamp TEXT NOT NULL,
filesystem TEXT NOT NULL,
size TEXT NOT NULL,
used TEXT NOT NULL,
available TEXT NOT NULL,
use_percent INTEGER NOT NULL,
mounted_on TEXT NOT NULL
>);

>CREATE INDEX idx_server_time ON dfinfo(server_name, timestamp);

故障排除

无法连接到远程服务器

  1. 确认SSH免密登录已正确设置
  2. 检查远程服务器是否可访问:ping remote-server
  3. 检查SSH连接:ssh remote-server "echo test"
  4. 确认远程服务器上的防火墙未阻止SSH连接

数据收集失败

  1. 检查脚本执行权限:chmod +x disk_monitor/*.sh disk_monitor/*.py
  2. 检查SQLite3是否已安装:sqlite3 --version
  3. 检查数据库文件权限:ls -la disk_monitor/disk_data.db
  4. 手动运行收集脚本测试:./disk_monitor/collect_disk_info.sh localhost

Web服务器启动失败

  1. 确认Python 3已安装:python3 --version
  2. 检查端口是否被占用:lsof -i :8000
  3. 尝试使用不同的端口:修改disk_monitor_server.py中的端口号
  4. 检查防火墙设置,确保允许访问指定端口

扩展和自定义

添加更多统计信息

要在Web界面添加更多统计信息,编辑 static/index.html 文件,在 <div class="stats"> 部分添加新的统计卡片。

自定义图表样式

系统使用Chart.js绘制图表。要自定义图表样式,编辑 static/index.html 文件中的 initChart() 函数。

添加数据导出功能

可以通过修改Web服务器脚本添加数据导出功能,例如导出为CSV或JSON格式。

现在我们需要确保所有脚本都有执行权限,这样用户可以直接运行它们。让我们使用execute_command工具来设置执行权限。

execute_command

1
>chmod +x disk_monitor/*.sh disk_monitor/*.py

我们已经完成了多服务器磁盘监控系统的所有必要组件:

  1. 数据收集脚本(本地和远程)
  2. Web服务器
  3. 前端界面
  4. 服务器配置文件
  5. 启动脚本
  6. 使用说明文档

让我们总结一下系统的主要功能和使用方法。

多服务器磁盘监控系统已完成

系统主要组件:

  1. collect_disk_info.sh - 本地服务器磁盘数据收集脚本
  2. collect_remote_disk_info.sh - 远程服务器磁盘数据收集脚本
  3. disk_monitor_server.py - Web服务器
  4. index.html - Web界面
  5. servers.conf - 服务器配置文件
  6. start_monitor.sh - 系统启动脚本

使用方法

  1. 编辑 servers.conf 文件,添加需要监控的服务器:

    1
    2
    3
    >localhost=127.0.0.1
    >web-server=192.168.1.101
    >db-server=db.example.com
  2. 对于远程服务器,设置SSH免密登录:

    1
    >ssh-copy-id username@remote-server
  3. 启动监控系统:

    1
    2
    >cd disk_monitor
    >./start_monitor.sh
  4. 访问Web界面:http://localhost:8000

系统将自动:

  • 每小时收集一次所有配置服务器的磁盘使用数据
  • 在Web界面上展示磁盘使用趋势图
  • 支持按服务器和文件系统筛选数据
  • 显示实时统计信息

详细的配置和使用说明请参考 README.md 文件。

着实震惊了一把。只能用惊叹❗️来形容了。并且想到了 ssh 远程登录服务器,配置密钥、验证远程服务器是否可以访问等多种情况,确实比我考虑的多多了。

但是他没有考虑配置 登录的用户信息,ssh 密钥信息等,然后相继把需求发给他

ssh 登录的用户名在哪里指定?

image-20250530153143587

服务器加上 可以自定义端口和证书

Craft 把服务器的配置信息有原来的文本格式,修改成了 json的格式,并给出了详细的配置示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"localhost": {
"name": "localhost",
"host": "127.0.0.1",
"user": "root",
"port": 22,
"key_path": "~/.ssh/id_rsa"
},
"web_server": {
"name": "web_server",
"host": "example.com",
"user": "admin",
"port": 2222,
"key_path": "/path/to/your/private/key"
}
}
image-20250530153619138

经过最终的确认,生成的页面如下:

image-20250530153721387

当然在使用的过程中也有不少问题,比如连接网络失败,点击重试一下就可以了。

image-20250530154526396

以下是本次的代码:开箱即用,在我本地mac 环境是可以直接运行成功的,如果遇到依赖包没有按照,比如我在使用的时候遇到解析json用到了 jq,手动安装即可。

image-20250530154210937

错误: 未找到jq。请安装jq用于解析JSON配置。
可以使用以下命令安装:

  • MacOS: brew install jq
  • Ubuntu/Debian: sudo apt-get install jq
  • CentOS/RHEL: sudo yum install jq

本次利用腾讯云代码助手 CodeBuddy 纯自然语言手搓一个服务器磁盘空间监控系统的代码如下,可以开箱即用。

https://github.com/aimdotsh/autodiy/tree/main

@CodeBuddy 官号 +添加 #AI 编程 #腾讯云代码助手 话题~

原文作者: liups.com

原文链接: http://liups.com/posts/codebuddy/

许可协议: 知识共享署名-非商业性使用 4.0 国际许可协议