业务需求:
1)监测IP不通时,将Cacti流量图发出来
2)流量超设定阈值时,将Cacti流量图发出来

从cacti上查看graph_id

配置文件:
{
    "client_info": {
        "1": {
            "name": "User1",
            "ip": "192.168.1.1",
            "bandwidth": 500,
            "graph_id" : 561,
            "threshold": 10 
        },
        "2": {
            "name": "User2",
            "ip": "192.168.1.2",
            "bandwidth": 220,
            "graph_id" : 596,
            "threshold": 0
        }
}

def Combine_Images_Vertically(images_list=[], output_path_filename=None):
    images = [Image.open(image) for image in images_list]
    widths, heights = zip(*(i.size for i in images))
    new_width = max(widths)
    new_height = sum(heights)
    new_image = Image.new('RGB', (new_width, new_height))
    y_offset = 0
    for img in images:
        new_image.paste(img, (0, y_offset))
        y_offset += img.height
    new_image.save(output_path_filename)
    if os.path.exists(output_path_filename):
        return True
    else:
        return False

def Ping_IP(ip):
    try:
        for _ in range(5):
            output = subprocess.run(['ping', '-c', '1', ip], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, timeout=5)
            if output.returncode == 0:
                return (ip, "alive")
        return (ip, "dead")
    except subprocess.TimeoutExpired:
        return (ip, "timeout")
    except Exception as e:
        return (ip, "error")
def Check_CrossLine_HK_VM(ip_list):
    results = []
    with concurrent.futures.ThreadPoolExecutor(max_workers=20) as executor:
        for result in executor.map(Ping_IP, ip_list):
            results.append(result)
    return results

def Delete_Old_Cacti_Files(graph_id, minutes_old=30):
    prefix1 = f"cacti_graph_{graph_id}_past_one_day_"
    prefix2 = f"cacti_graph_{graph_id}_past_half_hour_"
    prefix3 = f"cacti_graph_{graph_id}_combine_"
    prefix4 = f"cacti_graph_{graph_id}_past_week_"

    prefixes = [prefix1, prefix2, prefix3, prefix4]
    now = datetime.now()
    old_time = now - timedelta(minutes=minutes_old)
    old_time_prefix3 = now - timedelta(days=1)

    file_types = ['csv', 'png', 'svg']

    for prefix in prefixes:
        for file_type in file_types:
            file_pattern = os.path.join(IMG_WEB_DIR, f"{prefix}*.{file_type}")
            for file_path in glob(file_pattern):
                try:
                    file_name = os.path.basename(file_path)
                    timestamp_str = file_name.split(prefix)[1].split(f'.{file_type}')[0]
                    file_timestamp = int(timestamp_str)
                    file_mod_time = datetime.fromtimestamp(file_timestamp)
                    if prefix == prefix3:
                        if file_mod_time <= old_time_prefix3:
                            os.remove(file_path)
                            running_logger.info(f"Deleted file: {file_path}")
                    else:
                        if file_mod_time <= old_time:
                            os.remove(file_path)
                            running_logger.info(f"Deleted file: {file_path}")
                except Exception as e:
                    print(f"Error deleting file {file_path}: {e}")

def Monitor_Cacti_Traffic(erp=None,group_id=None):

    ip_list = []
    session = requests.session()
    header = {
        "User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36",
        }

    def tobit(value):
        if not isinstance(value, (int, float)):
            return '0b'
        for unit in ['b', 'K', 'M', 'G', 'T']:
            if value < 1000.0:
                return f"{value:.1f}{unit}"
            value /= 1000.0
        return f"{value:.1f}T"
    def Send_Image(graph_id, graph_name_descr, user_bandwidth, alarm_threshold, vip_check=False):
        try:
            res1 = session.get(Cacti_LOGIN_URL, headers=header).text
            c_token = re.findall('var csrfMagicToken = "(.*?)";', res1)[0]
            data = {
                "__csrf_magic": c_token,
                "action": "login",
                "login_username": Cacti_LOGIN_USER,
                "login_password": Cacti_LOGIN_PWD,
            }
            request1 = session.post(url=Cacti_LOGIN_URL, data=data, headers=header)
            if request1.status_code == 200:
                now = int(time.time())
                past_hour = now - 3600
                past_day = now - 86400
                image_url_past_hour = f"{Cacti_LOGIN_URL}/graph_image.php?local_graph_id={graph_id}&rra_id=0&graph_height=300&graph_width=1000&title_font_size=10&view_type=tree&graph_start={past_hour}&graph_end={now}"
                image_url_past_day = f"{Cacti_LOGIN_URL}/graph_image.php?local_graph_id={graph_id}&rra_id=0&graph_height=300&graph_width=1000&title_font_size=10&view_type=tree&graph_start={past_day}&graph_end={now}"

                request = session.get(url=image_url_past_hour, headers=header, timeout=10)
                aw1 = request.content
                _past_half_hour_save_path_svg = os.path.join(IMG_WEB_DIR, f"cacti_graph_{graph_id}_past_half_hour_{now}.svg")
                _past_half_hour_save_path_png = os.path.join(IMG_WEB_DIR, f"cacti_graph_{graph_id}_past_half_hour_{now}.png")
                with open(_past_half_hour_save_path_svg, 'wb') as file:
                    file.write(aw1)
                cairosvg.svg2png(url=_past_half_hour_save_path_svg,write_to=_past_half_hour_save_path_png)
                request = session.get(url=image_url_past_day, headers=header, timeout=10)
                aw2 = request.content
                _past_one_day_save_path_svg = os.path.join(IMG_WEB_DIR, f"cacti_graph_{graph_id}_past_one_day_{now}.svg")
                _past_one_day_save_path_png = os.path.join(IMG_WEB_DIR, f"cacti_graph_{graph_id}_past_one_day_{now}.png")
                with open(_past_one_day_save_path_svg, 'wb') as file:
                    file.write(aw2)
                cairosvg.svg2png(url=_past_one_day_save_path_svg,write_to=_past_one_day_save_path_png)

                rendered_images_fullpath_list = [_past_half_hour_save_path_png, _past_one_day_save_path_png]
                image_file_name_combine = f"cacti_graph_{graph_id}_combine_{now}.png"
                image_file_path_combine = os.path.join(IMG_WEB_DIR, image_file_name_combine)

                csv_url_past_day = f"{Cacti_LOGIN_URL}/graph_xport.php?local_graph_id={graph_id}&rra_id=0&graph_height=300&graph_width=1000&title_font_size=10&view_type=tree&graph_start={past_day}&graph_end={now}"
                _past_one_day_save_path_csv = os.path.join(IMG_WEB_DIR, f"cacti_graph_{graph_id}_past_one_day_{now}.csv")
                request = session.get(url=csv_url_past_day, headers=header, timeout=10)
                csv_ret = request.content
                with open(_past_one_day_save_path_csv, 'wb') as file:
                    file.write(csv_ret)

                with open(_past_one_day_save_path_csv, 'r', encoding='utf-8', newline='') as file:
                    reader = csv.reader(file)
                    inbounds = []
                    outbounds = []
                    data_rows = []
                    n = 0
                    for row in reader:
                        if 'NaN' not in row:
                            data_rows.append(row)
                        if 13 <= n <= 301:
                            if row[1] != 'NaN':
                                inbounds.append(float(row[1]))
                            if row[3] != 'NaN':
                                outbounds.append(float(row[3]))
                        n += 1
                    inbound_max1 = tobit(heapq.nlargest(1, inbounds)[0])
                    outbound_max1 = tobit(heapq.nlargest(1, outbounds)[0])
                    if data_rows:
                        last_row = data_rows[-1]
                        column_2 = float(last_row[1])
                        column_4 = float(last_row[3])
                    current_inbound = tobit(column_2)
                    current_outbound = tobit(column_4)
                max_current_without_unit = max(column_2,column_4)/1000/1000
                ouput_graph_name = f"{graph_name_descr} 当前/峰值/带宽{max(current_inbound,current_outbound)}/{max(inbound_max1,outbound_max1)}/{user_bandwidth}M"
                if (vip_check and max_current_without_unit < alarm_threshold) or vip_check == False:
                    if vip_check and max_current_without_unit < alarm_threshold:
                        running_logger.info(f"VIP客户低于阈值告警:{ouput_graph_name}")
                    if vip_check == False:
                        running_logger.info(f"默认告警,PING检测5次均失败:{ouput_graph_name}")
                    if Combine_Images_Vertically(rendered_images_fullpath_list, output_path_filename=image_file_path_combine):
                        BotSendInteractiveCardsMessage(erp=erp, group_id=group_id, img=image_file_name_combine, title=f"{ouput_graph_name}", deeplink_url=None)
                    else:
                        running_logger.info("Cacti合并图片失败")
            else :
                running_logger.info("Cacti登陆失败")
        except Exception as e:
            running_logger.info(f"{str(e)}")
            pass
    with open(CrossLine_Client_FILE, 'r', encoding='utf-8') as file:
        crossline_config = json.load(file)

    for client_id, client_details in crossline_config['client_info'].items():
        ip_list.append(client_details['ip'])

    # IP不通时发送监控图
    results = Check_CrossLine_HK_VM(ip_list)
    for result in results:
        if result[1] != "alive":
            for client_id, client_details in crossline_config['client_info'].items():
                if client_details['ip'] == result[0]:
                    graph_id = client_details['graph_id']
                    graph_name_descr = client_details['name']
                    user_bandwidth = client_details['bandwidth']
            Send_Image(graph_id, graph_name_descr, user_bandwidth,alarm_threshold=0,vip_check=False)

    # 当前流量<5%总流量时发送异常告警
    for client_id, client_details in crossline_config['client_info'].items():
        graph_id = client_details['graph_id']
        graph_name_descr = client_details['name']
        user_bandwidth = client_details['bandwidth']
        alarm_threshold = client_details['threshold']
        if user_bandwidth >= 25:
            Send_Image(graph_id, graph_name_descr, user_bandwidth, alarm_threshold, vip_check=True, )

    # 自动清理旧文件
    for client_id, client_details in crossline_config['client_info'].items():
        graph_id = client_details['graph_id']
        Delete_Old_Cacti_Files(graph_id=graph_id)