阿里云监控系统告警数据分析与报告生成脚本
脚本概述
这是一个用于分析阿里云监控系统告警数据并生成可视化报告的Python脚本。它能够自动获取最近7天的告警信息,进行数据分析,生成统计图表,并将报告发送到指定的钉钉群组。
个人博客
个人博客直达地址
网站不断完善中里面拥有大量的脚本,并且源码完全开放 欢迎纯白嫖。
效果图
主要功能
- 数据获取:通过阿里云CMS API获取最近7天的告警数据。
- 数据分析:对获取的告警数据进行统计和分析。
- 可视化:使用matplotlib和seaborn生成多种统计图表。
- 报告生成:创建包含统计摘要和可视化图表的综合报告。
- OSS上传:将生成的报告图片上传到阿里云OSS存储。
- 消息推送:通过钉钉机器人将报告发送到指定群组。
详细功能说明
1. 数据获取
- 使用
create_client()
函数创建阿里云CMS客户端。 - 通过
describe_alert_history_list_with_options()
函数获取告警历史数据。
2. 数据分析
- 处理获取的告警数据,提取关键信息如实例名称、指标名称、告警级别等。
- 使用pandas进行数据处理和统计分析。
3. 可视化
generate_report()
函数生成多个统计图表:
- Top 5 报警次数最多的实例
- Top 5 实例的平均响应时间
- Top 5 实例的最常见报警规则
- Top 5 报警规则的报警次数
- Top 5 报警规则的平均报警值
- 报警等级分布
4. 报告生成
generate_summary_report()
函数生成包含实例报警汇总的文字报告。- 将统计图表和摘要报告整合为一个完整的报告。
5. OSS上传
- 使用
upload_image()
函数将生成的报告图片上传到阿里云OSS。
6. 消息推送
send_to_webhook()
函数将报告通过钉钉机器人发送到指定群组。
使用说明
- 确保已安装所有必要的Python库:alibabacloud_cms20190101, pandas, matplotlib, seaborn, oss2。
- 配置环境变量:
ALIBABA_CLOUD_ACCESS_KEY_ID
:阿里云AccessKey IDALIBABA_CLOUD_ACCESS_KEY_SECRET
:阿里云AccessKey SecretOSS_ACCESS_KEY_ID
:阿里云OSS的AccessKey IDOSS_ACCESS_KEY_SECRET
:阿里云OSS的AccessKey Secret
- 修改脚本中的以下参数:
- OSS的bucket名称和endpoint
- 钉钉机器人的webhook URL
- 运行脚本:
python script_name.py
注意事项
- API访问限制:注意阿里云API的访问频率限制,避免频繁调用导致被限制。
- 数据安全:脚本处理敏感的监控数据,确保运行环境的安全性。
- 依赖管理:确保所有依赖库的版本兼容,建议使用虚拟环境。
- 错误处理:脚本包含基本的错误处理,但在生产环境中可能需要更健壮的错误处理机制。
- 定制化:根据实际需求,可能需要调整图表样式、报告内容等。
- 资源消耗:生成大量图表可能消耗较多CPU和内存,注意监控资源使用。
- 字体配置:确保系统中安装了指定的中文字体(wqy-zenhei),否则可能导致中文显示问题。
- OSS配置:确保OSS配置正确,包括访问权限和存储策略。
- 钉钉限制:注意钉钉机器人的消息发送频率和大小限制。
代码结构
脚本主要包含以下几个关键方法:
create_client()
: 创建阿里云CMS客户端generate_time_range()
: 生成查询时间范围upload_image()
: 上传图片到OSSsend_to_webhook()
: 发送报告到钉钉generate_report()
: 生成可视化报告generate_summary_report()
: 生成摘要报告
#!/usr/bin/python3
from datetime import datetime
import time
import os
import json
import requests
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from alibabacloud_cms20190101.client import Client as Cms20190101Client
from alibabacloud_tea_openapi import models as open_api_models
from alibabacloud_cms20190101 import models as cms_20190101_models
from alibabacloud_tea_util import models as util_models
from io import BytesIO
from typing import List
from matplotlib.font_manager import FontProperties
import oss2
# 设置字体路径
font_path = "/usr/share/fonts/wqy-zenhei/wqy-zenhei.ttc"
font_prop = FontProperties(fname=font_path)
plt.rcParams['font.family'] = font_prop.get_name()
plt.rcParams.update({'font.size': 14})
class Sample:
@staticmethod
def create_client() -> Cms20190101Client:
config = open_api_models.Config(
access_key_id=os.environ['ALIBABA_CLOUD_ACCESS_KEY_ID'], # 脱敏
access_key_secret=os.environ['ALIBABA_CLOUD_ACCESS_KEY_SECRET'] # 脱敏
)
config.endpoint = f'metrics.cn-hongkong.aliyuncs.com'
return Cms20190101Client(config)
@staticmethod
def generate_time_range() -> (int, int):
end_time = int(time.time() * 1000)
start_time = end_time - 7 * 24 * 60 * 60 * 1000
print(f"生成时间范围: {start_time} - {end_time}")
return start_time, end_time
@staticmethod
def save_image(buffer) -> str:
timestamp = datetime.now().strftime('%Y%m%d%H%M%S')
file_path = f'/opt/image/报警历史汇总_{timestamp}.png'
with open(file_path, 'wb') as f:
f.write(buffer.getvalue())
return file_path
@staticmethod
def upload_image(image_buffer, bucket_name):
timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
object_name = f'阿里云_{timestamp}.png'
auth = oss2.Auth(os.getenv('OSS_ACCESS_KEY_ID'), os.getenv('OSS_ACCESS_KEY_SECRET')) # 脱敏
bucket = oss2.Bucket(auth, 'https://oss-cn-hangzhou.aliyuncs.com', bucket_name)
bucket.put_object(object_name, image_buffer)
print(f"文件上传成功: {object_name}")
return object_name
@staticmethod
def send_to_webhook(image_url: str, markdown_content: str, total_alert_count: int, start_time: int,
end_time: int) -> None:
webhook_url = "https://oapi.dingtalk.com/robot/send?access_token=YOUR_ACCESS_TOKEN" # 脱敏
headers = {'Content-Type': 'application/json'}
start_date = datetime.fromtimestamp(start_time / 1000).strftime('%Y-%m-%d')
end_date = datetime.fromtimestamp(end_time / 1000).strftime('%Y-%m-%d')
data = {
"msgtype": "actionCard",
"at": {
"isAtAll": True # @所有人
},
"actionCard": {
"title": "阿里云报警历史汇总",
"text": f"### TEST阿里云报警历史汇总\n\n"
f"**备注**: 劳烦各位针对自己负责的机器或者服务看看为什么报警以及看看能否优化一下"
f"**查询日期范围**: {start_date} 至 {end_date}\n\n"
f"{markdown_content}\n"
f"![报警历史汇总]({image_url})",
"btns": [
{
"title": "查看详情",
"actionURL": image_url
}
],
"mark": "TEST"
}
}
response = requests.post(webhook_url, headers=headers, data=json.dumps(data))
print("Webhook 响应状态码:", response.status_code)
print("Webhook 响应内容:", response.text)
response.raise_for_status()
print("消息发送成功")
@staticmethod
def main(args: List[str]) -> None:
client = Sample.create_client()
start_time, end_time = Sample.generate_time_range()
all_alerts = []
page = 1
page_size = 100
while True:
describe_alert_history_list_request = cms_20190101_models.DescribeAlertHistoryListRequest(
start_time=str(start_time),
end_time=str(end_time),
page_size=page_size,
state='ALARM',
status='0',
page=page
)
runtime = util_models.RuntimeOptions()
try:
response = client.describe_alert_history_list_with_options(describe_alert_history_list_request, runtime)
alarm_history_list = response.body.alarm_history_list.alarm_history
if not alarm_history_list:
break
all_alerts.extend(alarm_history_list)
page += 1
except Exception as error:
print(f"发生错误: {str(error)}")
return
processed_alerts = []
for item in all_alerts:
try:
value = item.value
if isinstance(value, str):
try:
value = json.loads(value)
except json.JSONDecodeError:
try:
value = float(value)
except ValueError:
value = None
dimensions = json.loads(item.dimensions)
processed_alerts.append({
'InstanceName': item.instance_name,
'MetricName': item.metric_name,
'Level': item.level,
'AlertTime': pd.to_datetime(item.alert_time, unit='ms'),
'EvaluationCount': item.evaluation_count,
'RuleName': item.rule_name,
'LastTime': pd.to_datetime(item.last_time, unit='ms'),
'Value': float(value) if isinstance(value, (int, float)) else None,
'InstanceId': dimensions.get('instanceId', None),
'Role': dimensions.get('role', None)
})
except Exception as e:
print(f"处理记录时发生错误: {str(e)}")
df = pd.DataFrame(processed_alerts)
if not df.empty:
# 计算总报警数量
total_alert_count = len(processed_alerts) # 使用原始数据列表的长度
image_buffer = Sample.generate_report(df)
summary_report = Sample.generate_summary_report(df)
# 上传到 OSS
object_name = Sample.upload_image(image_buffer, '自己OSS的名称')
# 生成图片 URL
image_url = f"https://自己OSS的名称.oss-cn-hangzhou.aliyuncs.com/{object_name}"
# 发送到 Webhook
Sample.send_to_webhook(image_url, summary_report, total_alert_count, start_time, end_time)
else:
print("没有数据生成报告")
@staticmethod
def generate_report(df: pd.DataFrame) -> BytesIO:
plt.figure(figsize=(50, 36), dpi=100) # 增加图像大小和分辨率
sns.set(style="whitegrid")
# 统计每个实例的报警次数、平均响应时间和最常见的报警规则
instance_summary = df.groupby('InstanceName').agg(
AlertCount=('EvaluationCount', 'sum'),
AverageResponseTime=('LastTime', lambda x: (x.max() - x.min()).total_seconds() / len(x)),
MostCommonRule=('RuleName', lambda x: x.value_counts().idxmax())
).sort_values('AlertCount', ascending=False).head(5)
# 2. Top 5 实例的报警次数
plt.subplot(4, 2, 8)
instance_summary = df.groupby('InstanceName').agg(AlertCount=('EvaluationCount', 'sum')).sort_values('AlertCount', ascending=False).head(5)
sns.barplot(x=instance_summary.AlertCount, y=instance_summary.index, palette='Blues_d', edgecolor='black')
plt.title("Top 5 报警次数最多的实例", fontproperties=font_prop, fontsize=45)
plt.xlabel("报警次数", fontproperties=font_prop, fontsize=35)
plt.ylabel("实例名称", fontproperties=font_prop, fontsize=35)
plt.xticks(rotation=45, ha='right', fontsize=30)
plt.yticks(fontproperties=font_prop, fontsize=30)
plt.grid(axis='x', linestyle='--', alpha=0.7)
# 3. Top 5 实例的平均响应时间
plt.subplot(4, 2, 3)
instance_summary['AverageResponseTime'] = df.groupby('InstanceName').apply(
lambda x: (x['LastTime'].max() - x['LastTime'].min()).total_seconds() / len(x)
).sort_values(ascending=False).head(5)
sns.barplot(x=instance_summary.AverageResponseTime, y=instance_summary.index, palette='Reds_d', edgecolor='black')
plt.title("Top 5 实例的平均响应时间", fontproperties=font_prop, fontsize=45)
plt.xlabel("平均响应时间 (秒)", fontproperties=font_prop, fontsize=35)
plt.ylabel("实例名称", fontproperties=font_prop, fontsize=35)
plt.xticks(rotation=45, ha='right', fontsize=30)
plt.yticks(fontproperties=font_prop, fontsize=30)
plt.grid(axis='x', linestyle='--', alpha=0.7)
# 4. Top 5 实例的最常见报警规则
plt.subplot(4, 2, 4)
most_common_rule = df.groupby('InstanceName')['RuleName'].apply(lambda x: x.value_counts().idxmax())
rule_counts = df[df['InstanceName'].isin(most_common_rule.index)].groupby(['InstanceName', 'RuleName']).size().unstack(fill_value=0)
rule_counts = rule_counts.loc[most_common_rule.index].T
rule_counts.plot(kind='barh', stacked=True, colormap='Set1', ax=plt.gca())
plt.title("Top 5 实例的最常见报警规则", fontproperties=font_prop, fontsize=45)
plt.xlabel("报警次数", fontproperties=font_prop, fontsize=35)
plt.ylabel("实例名称", fontproperties=font_prop, fontsize=35)
plt.xticks(fontproperties=font_prop, fontsize=30)
plt.yticks(fontproperties=font_prop, fontsize=30)
plt.grid(axis='x', linestyle='--', alpha=0.7)
# 5. Top 5 报警规则的报警次数
plt.subplot(4, 2, 5)
rule_summary = df.groupby('RuleName').agg(AlertCount=('EvaluationCount', 'sum')).sort_values('AlertCount', ascending=False).head(5)
sns.barplot(x=rule_summary.AlertCount, y=rule_summary.index, palette='Purples_d', edgecolor='black')
plt.title("Top 5 报警规则的报警次数", fontproperties=font_prop, fontsize=45)
plt.xlabel("报警次数", fontproperties=font_prop, fontsize=35)
plt.ylabel("报警规则", fontproperties=font_prop, fontsize=35)
plt.xticks(rotation=45, ha='right', fontsize=30)
plt.yticks(fontproperties=font_prop, fontsize=30)
plt.grid(axis='x', linestyle='--', alpha=0.7)
# 6. Top 5 报警规则的平均报警值
plt.subplot(4, 2, 6)
rule_summary['AverageAlertValue'] = df.groupby('RuleName').agg(AverageAlertValue=('Value', 'mean')).sort_values('AverageAlertValue', ascending=False).head(5)
sns.barplot(x=rule_summary.AverageAlertValue, y=rule_summary.index, palette='Oranges_d', edgecolor='black')
plt.title("Top 5 报警规则的平均报警值", fontproperties=font_prop, fontsize=45)
plt.xlabel("平均报警值", fontproperties=font_prop, fontsize=35)
plt.ylabel("报警规则", fontproperties=font_prop, fontsize=35)
plt.xticks(rotation=45, ha='right', fontsize=30)
plt.yticks(fontproperties=font_prop, fontsize=30)
plt.grid(axis='x', linestyle='--', alpha=0.7)
# 7. 报警等级分布
plt.subplot(4, 2, 7)
level_summary = df.groupby('Level').size()
sns.barplot(x=level_summary.values, y=level_summary.index, palette='Greys_d', edgecolor='black')
plt.title("报警等级分布", fontproperties=font_prop, fontsize=45)
plt.xlabel("报警次数", fontproperties=font_prop, fontsize=35)
plt.ylabel("报警等级", fontproperties=font_prop, fontsize=35)
plt.xticks(rotation=45, ha='right', fontsize=30)
plt.yticks(fontproperties=font_prop, fontsize=30)
plt.grid(axis='x', linestyle='--', alpha=0.7)
plt.tight_layout()
buffer = BytesIO()
plt.savefig(buffer, format='png')
buffer.seek(0)
return buffer
@staticmethod
def generate_summary_report(df: pd.DataFrame) -> str:
# 计算每个实例的报警汇总
instance_summary = df.groupby('InstanceName').agg(
AlertCount=('EvaluationCount', 'sum'),
AverageResponseTime=('LastTime', lambda x: (x.max() - x.min()).total_seconds() / len(x)),
MostCommonRule=('RuleName', lambda x: x.value_counts().idxmax())
).sort_values('AlertCount', ascending=False).head(5)
# 生成汇总报告
report = "#### 实例报警汇总报告\n"
for index, row in instance_summary.iterrows():
report += f"- **{index}**: 报警次数: {row['AlertCount']}, 平均响应时间: {row['AverageResponseTime']}秒, 最常见的报警规则: {row['MostCommonRule']}\n"
return report
if __name__ == "__main__":
import sys
Sample.main(sys.argv)