feat:自动更换ip+流量监控
This commit is contained in:
338
services/traffic_monitor.py
Normal file
338
services/traffic_monitor.py
Normal file
@@ -0,0 +1,338 @@
|
||||
"""流量监控服务 - 获取 EC2/Lightsail 流量数据"""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import boto3
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
SHANGHAI_TZ = ZoneInfo("Asia/Shanghai")
|
||||
|
||||
|
||||
def create_cloudwatch_client(region: str, aws_access_key: str, aws_secret_key: str):
|
||||
"""创建 CloudWatch 客户端"""
|
||||
return boto3.client(
|
||||
"cloudwatch",
|
||||
region_name=region,
|
||||
aws_access_key_id=aws_access_key,
|
||||
aws_secret_access_key=aws_secret_key,
|
||||
)
|
||||
|
||||
|
||||
def get_ec2_traffic(
|
||||
region: str,
|
||||
instance_id: str,
|
||||
aws_access_key: str,
|
||||
aws_secret_key: str,
|
||||
start_time: datetime,
|
||||
end_time: datetime,
|
||||
period: int = 3600,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
获取 EC2 实例的流量数据
|
||||
|
||||
Args:
|
||||
region: AWS 区域
|
||||
instance_id: EC2 实例 ID
|
||||
aws_access_key: AWS Access Key
|
||||
aws_secret_key: AWS Secret Key
|
||||
start_time: 开始时间
|
||||
end_time: 结束时间
|
||||
period: 数据点间隔(秒),默认1小时
|
||||
|
||||
Returns:
|
||||
{
|
||||
"network_in": 下载流量(字节),
|
||||
"network_out": 上传流量(字节),
|
||||
"data_points": 详细数据点列表
|
||||
}
|
||||
"""
|
||||
try:
|
||||
cloudwatch = create_cloudwatch_client(region, aws_access_key, aws_secret_key)
|
||||
|
||||
# 获取 NetworkIn(下载)
|
||||
network_in_response = cloudwatch.get_metric_statistics(
|
||||
Namespace="AWS/EC2",
|
||||
MetricName="NetworkIn",
|
||||
Dimensions=[{"Name": "InstanceId", "Value": instance_id}],
|
||||
StartTime=start_time,
|
||||
EndTime=end_time,
|
||||
Period=period,
|
||||
Statistics=["Sum"],
|
||||
)
|
||||
|
||||
# 获取 NetworkOut(上传)
|
||||
network_out_response = cloudwatch.get_metric_statistics(
|
||||
Namespace="AWS/EC2",
|
||||
MetricName="NetworkOut",
|
||||
Dimensions=[{"Name": "InstanceId", "Value": instance_id}],
|
||||
StartTime=start_time,
|
||||
EndTime=end_time,
|
||||
Period=period,
|
||||
Statistics=["Sum"],
|
||||
)
|
||||
|
||||
# 计算总流量
|
||||
network_in_total = sum(dp["Sum"] for dp in network_in_response.get("Datapoints", []))
|
||||
network_out_total = sum(dp["Sum"] for dp in network_out_response.get("Datapoints", []))
|
||||
|
||||
# 合并数据点用于图表
|
||||
data_points = []
|
||||
in_points = {dp["Timestamp"]: dp["Sum"] for dp in network_in_response.get("Datapoints", [])}
|
||||
out_points = {dp["Timestamp"]: dp["Sum"] for dp in network_out_response.get("Datapoints", [])}
|
||||
|
||||
all_timestamps = sorted(set(in_points.keys()) | set(out_points.keys()))
|
||||
for ts in all_timestamps:
|
||||
data_points.append({
|
||||
"timestamp": ts.astimezone(SHANGHAI_TZ).isoformat(),
|
||||
"network_in": in_points.get(ts, 0),
|
||||
"network_out": out_points.get(ts, 0),
|
||||
})
|
||||
|
||||
return {
|
||||
"ok": True,
|
||||
"network_in": network_in_total,
|
||||
"network_out": network_out_total,
|
||||
"total": network_in_total + network_out_total,
|
||||
"data_points": data_points,
|
||||
}
|
||||
except Exception as e:
|
||||
logger.exception("Failed to get EC2 traffic for %s", instance_id)
|
||||
return {"ok": False, "message": str(e), "network_in": 0, "network_out": 0, "total": 0, "data_points": []}
|
||||
|
||||
|
||||
def get_lightsail_traffic(
|
||||
region: str,
|
||||
instance_name: str,
|
||||
aws_access_key: str,
|
||||
aws_secret_key: str,
|
||||
start_time: datetime,
|
||||
end_time: datetime,
|
||||
period: int = 3600,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
获取 Lightsail 实例的流量数据
|
||||
|
||||
Args:
|
||||
region: AWS 区域
|
||||
instance_name: Lightsail 实例名称
|
||||
aws_access_key: AWS Access Key
|
||||
aws_secret_key: AWS Secret Key
|
||||
start_time: 开始时间
|
||||
end_time: 结束时间
|
||||
period: 数据点间隔(秒),默认1小时
|
||||
|
||||
Returns:
|
||||
{
|
||||
"network_in": 下载流量(字节),
|
||||
"network_out": 上传流量(字节),
|
||||
"data_points": 详细数据点列表
|
||||
}
|
||||
"""
|
||||
try:
|
||||
lightsail = boto3.client(
|
||||
"lightsail",
|
||||
region_name=region,
|
||||
aws_access_key_id=aws_access_key,
|
||||
aws_secret_access_key=aws_secret_key,
|
||||
)
|
||||
|
||||
# 获取 NetworkIn(下载)
|
||||
network_in_response = lightsail.get_instance_metric_data(
|
||||
instanceName=instance_name,
|
||||
metricName="NetworkIn",
|
||||
period=period,
|
||||
startTime=start_time,
|
||||
endTime=end_time,
|
||||
unit="Bytes",
|
||||
statistics=["Sum"],
|
||||
)
|
||||
|
||||
# 获取 NetworkOut(上传)
|
||||
network_out_response = lightsail.get_instance_metric_data(
|
||||
instanceName=instance_name,
|
||||
metricName="NetworkOut",
|
||||
period=period,
|
||||
startTime=start_time,
|
||||
endTime=end_time,
|
||||
unit="Bytes",
|
||||
statistics=["Sum"],
|
||||
)
|
||||
|
||||
# 计算总流量
|
||||
in_data = network_in_response.get("metricData", [])
|
||||
out_data = network_out_response.get("metricData", [])
|
||||
|
||||
network_in_total = sum(dp.get("sum", 0) for dp in in_data)
|
||||
network_out_total = sum(dp.get("sum", 0) for dp in out_data)
|
||||
|
||||
# 合并数据点
|
||||
data_points = []
|
||||
in_points = {dp["timestamp"]: dp.get("sum", 0) for dp in in_data}
|
||||
out_points = {dp["timestamp"]: dp.get("sum", 0) for dp in out_data}
|
||||
|
||||
all_timestamps = sorted(set(in_points.keys()) | set(out_points.keys()))
|
||||
for ts in all_timestamps:
|
||||
data_points.append({
|
||||
"timestamp": ts.astimezone(SHANGHAI_TZ).isoformat(),
|
||||
"network_in": in_points.get(ts, 0),
|
||||
"network_out": out_points.get(ts, 0),
|
||||
})
|
||||
|
||||
return {
|
||||
"ok": True,
|
||||
"network_in": network_in_total,
|
||||
"network_out": network_out_total,
|
||||
"total": network_in_total + network_out_total,
|
||||
"data_points": data_points,
|
||||
}
|
||||
except Exception as e:
|
||||
logger.exception("Failed to get Lightsail traffic for %s", instance_name)
|
||||
return {"ok": False, "message": str(e), "network_in": 0, "network_out": 0, "total": 0, "data_points": []}
|
||||
|
||||
|
||||
def get_machine_traffic(
|
||||
aws_service: str,
|
||||
region: str,
|
||||
instance_id: str,
|
||||
aws_access_key: str,
|
||||
aws_secret_key: str,
|
||||
start_time: datetime,
|
||||
end_time: datetime,
|
||||
period: int = 3600,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
统一接口:根据服务类型获取流量数据
|
||||
"""
|
||||
if aws_service == "lightsail":
|
||||
return get_lightsail_traffic(
|
||||
region=region,
|
||||
instance_name=instance_id,
|
||||
aws_access_key=aws_access_key,
|
||||
aws_secret_key=aws_secret_key,
|
||||
start_time=start_time,
|
||||
end_time=end_time,
|
||||
period=period,
|
||||
)
|
||||
else:
|
||||
return get_ec2_traffic(
|
||||
region=region,
|
||||
instance_id=instance_id,
|
||||
aws_access_key=aws_access_key,
|
||||
aws_secret_key=aws_secret_key,
|
||||
start_time=start_time,
|
||||
end_time=end_time,
|
||||
period=period,
|
||||
)
|
||||
|
||||
|
||||
def get_current_month_traffic(
|
||||
aws_service: str,
|
||||
region: str,
|
||||
instance_id: str,
|
||||
aws_access_key: str,
|
||||
aws_secret_key: str,
|
||||
) -> dict[str, Any]:
|
||||
"""获取当月流量数据"""
|
||||
now = datetime.now(SHANGHAI_TZ)
|
||||
start_of_month = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
|
||||
|
||||
return get_machine_traffic(
|
||||
aws_service=aws_service,
|
||||
region=region,
|
||||
instance_id=instance_id,
|
||||
aws_access_key=aws_access_key,
|
||||
aws_secret_key=aws_secret_key,
|
||||
start_time=start_of_month,
|
||||
end_time=now,
|
||||
period=3600, # 每小时一个数据点
|
||||
)
|
||||
|
||||
|
||||
def get_current_day_traffic(
|
||||
aws_service: str,
|
||||
region: str,
|
||||
instance_id: str,
|
||||
aws_access_key: str,
|
||||
aws_secret_key: str,
|
||||
) -> dict[str, Any]:
|
||||
"""获取当日流量数据"""
|
||||
now = datetime.now(SHANGHAI_TZ)
|
||||
start_of_day = now.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
|
||||
return get_machine_traffic(
|
||||
aws_service=aws_service,
|
||||
region=region,
|
||||
instance_id=instance_id,
|
||||
aws_access_key=aws_access_key,
|
||||
aws_secret_key=aws_secret_key,
|
||||
start_time=start_of_day,
|
||||
end_time=now,
|
||||
period=300, # 每5分钟一个数据点
|
||||
)
|
||||
|
||||
|
||||
def get_all_time_traffic(
|
||||
aws_service: str,
|
||||
region: str,
|
||||
instance_id: str,
|
||||
aws_access_key: str,
|
||||
aws_secret_key: str,
|
||||
created_at: datetime = None,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
获取建站至今的总流量数据
|
||||
|
||||
注意: CloudWatch 数据保留期限有限:
|
||||
- 小于60秒的数据点保留3小时
|
||||
- 60秒(1分钟)的数据点保留15天
|
||||
- 300秒(5分钟)的数据点保留63天
|
||||
- 3600秒(1小时)的数据点保留455天(约15个月)
|
||||
|
||||
因此这里只能获取最近约15个月的数据
|
||||
"""
|
||||
now = datetime.now(SHANGHAI_TZ)
|
||||
|
||||
# 如果提供了创建时间,使用它;否则使用15个月前
|
||||
if created_at:
|
||||
# 确保时区正确
|
||||
if created_at.tzinfo is None:
|
||||
start_time = created_at.replace(tzinfo=SHANGHAI_TZ)
|
||||
else:
|
||||
start_time = created_at.astimezone(SHANGHAI_TZ)
|
||||
else:
|
||||
# CloudWatch 最多保留约15个月的小时级数据
|
||||
start_time = now - timedelta(days=455)
|
||||
|
||||
return get_machine_traffic(
|
||||
aws_service=aws_service,
|
||||
region=region,
|
||||
instance_id=instance_id,
|
||||
aws_access_key=aws_access_key,
|
||||
aws_secret_key=aws_secret_key,
|
||||
start_time=start_time,
|
||||
end_time=now,
|
||||
period=86400, # 每天一个数据点
|
||||
)
|
||||
|
||||
|
||||
def format_bytes(bytes_value: float) -> str:
|
||||
"""格式化字节数为可读格式"""
|
||||
if bytes_value < 0:
|
||||
return "0 B"
|
||||
|
||||
units = ["B", "KB", "MB", "GB", "TB"]
|
||||
unit_index = 0
|
||||
value = float(bytes_value)
|
||||
|
||||
while value >= 1024 and unit_index < len(units) - 1:
|
||||
value /= 1024
|
||||
unit_index += 1
|
||||
|
||||
if unit_index == 0:
|
||||
return f"{int(value)} {units[unit_index]}"
|
||||
return f"{value:.2f} {units[unit_index]}"
|
||||
Reference in New Issue
Block a user