阿里云监控系统告警数据分析与报告生成脚本

阿里云监控系统告警数据分析与报告生成脚本

脚本概述

这是一个用于分析阿里云监控系统告警数据并生成可视化报告的Python脚本。它能够自动获取最近7天的告警信息,进行数据分析,生成统计图表,并将报告发送到指定的钉钉群组。

个人博客

个人博客直达地址
网站不断完善中里面拥有大量的脚本,并且源码完全开放 欢迎纯白嫖。

效果图

在这里插入图片描述
在这里插入图片描述

主要功能

  1. 数据获取:通过阿里云CMS API获取最近7天的告警数据。
  2. 数据分析:对获取的告警数据进行统计和分析。
  3. 可视化:使用matplotlib和seaborn生成多种统计图表。
  4. 报告生成:创建包含统计摘要和可视化图表的综合报告。
  5. OSS上传:将生成的报告图片上传到阿里云OSS存储。
  6. 消息推送:通过钉钉机器人将报告发送到指定群组。

详细功能说明

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()函数将报告通过钉钉机器人发送到指定群组。

使用说明

  1. 确保已安装所有必要的Python库:alibabacloud_cms20190101, pandas, matplotlib, seaborn, oss2。
  2. 配置环境变量:
    • ALIBABA_CLOUD_ACCESS_KEY_ID:阿里云AccessKey ID
    • ALIBABA_CLOUD_ACCESS_KEY_SECRET:阿里云AccessKey Secret
    • OSS_ACCESS_KEY_ID:阿里云OSS的AccessKey ID
    • OSS_ACCESS_KEY_SECRET:阿里云OSS的AccessKey Secret
  3. 修改脚本中的以下参数:
    • OSS的bucket名称和endpoint
    • 钉钉机器人的webhook URL
  4. 运行脚本:python script_name.py

注意事项

  1. API访问限制:注意阿里云API的访问频率限制,避免频繁调用导致被限制。
  2. 数据安全:脚本处理敏感的监控数据,确保运行环境的安全性。
  3. 依赖管理:确保所有依赖库的版本兼容,建议使用虚拟环境。
  4. 错误处理:脚本包含基本的错误处理,但在生产环境中可能需要更健壮的错误处理机制。
  5. 定制化:根据实际需求,可能需要调整图表样式、报告内容等。
  6. 资源消耗:生成大量图表可能消耗较多CPU和内存,注意监控资源使用。
  7. 字体配置:确保系统中安装了指定的中文字体(wqy-zenhei),否则可能导致中文显示问题。
  8. OSS配置:确保OSS配置正确,包括访问权限和存储策略。
  9. 钉钉限制:注意钉钉机器人的消息发送频率和大小限制。

代码结构

脚本主要包含以下几个关键方法:

  1. create_client(): 创建阿里云CMS客户端
  2. generate_time_range(): 生成查询时间范围
  3. upload_image(): 上传图片到OSS
  4. send_to_webhook(): 发送报告到钉钉
  5. generate_report(): 生成可视化报告
  6. 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)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

脚本小能手

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值