在 Ansible 模板中,如果你的 Python 脚本里有大量 {}、f""、或者其他 Jinja 会误解析的语法,就需要用 {% raw %}…{% endraw %} 把它们包起来,只在需要替换变量的那一行单独“放行”。例如:
{% raw %}
#!/usr/bin/env python3
import subprocess
import re
import socket
import time
from datetime import datetime
{% endraw %}
# 输出到的文件
PROM_FILE = "{{ node_exporter_textfile_dir }}/iotop_metrics.prom"
{% raw %}
# 单位换算
def parse_size(size_str):
size_str = size_str.strip()
if size_str.endswith("/s"):
size_str = size_str[:-2].strip()
m = re.match(r"([\d\.]+)\s*([BKMGTP]?)(B)?", size_str, re.I)
if not m:
return 0.0
val = float(m.group(1))
unit = m.group(2).upper()
unit_map = {
"": 1/1024, "B": 1/1024,
"K": 1, "M": 1024, "G": 1024**2,
"T": 1024**3, "P": 1024**4,
}
return val * unit_map.get(unit, 1)
# 用正则提取每列
iotop_line_re = re.compile(
r"^\s*(\d+)\s+(\S+)\s+(\S+)\s+([\d\.]+\s+\S+/s)\s+([\d\.]+\s+\S+/s)\s+([\d\.]+\s+%)\s+([\d\.]+\s+%)\s+(.*)$"
)
# 运行 iotop 并解析
def parse_iotop():
res = subprocess.run(
["sudo", "iotop", "-b", "-n", "1", "-P"],
capture_output=True, text=True, check=True
)
lines = res.stdout.splitlines()
total_read = total_write = 0.0
for line in lines:
if "Total DISK READ:" in line and "Total DISK WRITE:" in line:
left, right = line.split("|", 1)
total_read = parse_size(left.split("Total DISK READ:")[1].strip())
total_write = parse_size(right.split("Total DISK WRITE:")[1].strip())
break
procs = []
for line in lines:
line = line.strip()
m = iotop_line_re.match(line)
if not m:
continue
pid, prio, user, d_read, d_write, swapin, io, cmd = m.groups()
read_kb = parse_size(d_read)
write_kb = parse_size(d_write)
if read_kb == 0.0 and write_kb == 0.0:
continue
cmd = cmd.replace('"', '\\"')
procs.append((pid, user, cmd, read_kb, write_kb))
return total_read, total_write, procs
# 输出 Prometheus 格式指标
def write_metrics():
hostname = socket.gethostname()
now = datetime.utcnow().isoformat()
total_read, total_write, procs = parse_iotop()
lines = []
lines.append(f"# Timestamp: {now}")
# 指标定义
lines.append("# HELP proc_io_read_kbps Process disk read in KB/s")
lines.append("# TYPE proc_io_read_kbps gauge")
lines.append("# HELP proc_io_write_kbps Process disk write in KB/s")
lines.append("# TYPE proc_io_write_kbps gauge")
lines.append("# HELP proc_io_total_read_kbps Total disk read in KB/s")
lines.append("# TYPE proc_io_total_read_kbps gauge")
lines.append("# HELP proc_io_total_write_kbps Total disk write in KB/s")
lines.append("# TYPE proc_io_total_write_kbps gauge")
# 总量
lines.append(f'proc_io_total_read_kbps{{hostname="{hostname}"}} {total_read}')
lines.append(f'proc_io_total_write_kbps{{hostname="{hostname}"}} {total_write}')
# 每进程
for pid, user, cmd, r, w in procs:
labels = f'hostname="{hostname}",pid="{pid}",user="{user}",command="{cmd}"'
lines.append(f'proc_io_read_kbps{{{labels}}} {r}')
lines.append(f'proc_io_write_kbps{{{labels}}} {w}')
# 写入指标文件
with open(PROM_FILE, "w") as f:
f.write("\n".join(lines) + "\n")
# 主循环(不启动 web,仅周期更新)
def main():
while True:
try:
write_metrics()
except Exception as e:
print(f"[ERROR] {e}", flush=True)
time.sleep(1)
if __name__ == "__main__":
main()
{% endraw %}