Explorar el Código

feat:添加成本资源监控脚本

zhaohaipeng hace 2 días
padre
commit
c8f534e319

+ 7 - 6
resource/enums/region.py

@@ -2,12 +2,13 @@ from enum import Enum
 
 
 class Region(Enum):
-    HANG_ZHOU = ("cn-hangzhou", "r-kvstore.aliyuncs.com", "r-kvstore-vpc.cn-hangzhou.aliyuncs.com", "杭州")
-    HONG_KONG = ("cn-hongkong", "r-kvstore.cn-hongkong.aliyuncs.com", "r-kvstore-vpc.cn-hongkong.aliyuncs.com", "香港")
-    SOUTHEAST = ("ap-southeast-1", "r-kvstore.ap-southeast-1.aliyuncs.com", "r-kvstore-vpc.ap-southeast-1.aliyuncs.com", "新加坡")
-    US_WEST = ("us-west-1", "r-kvstore.us-west-1.aliyuncs.com", "r-kvstore-vpc.us-west-1.aliyuncs.com", "美国(硅谷)")
+    HANG_ZHOU = ("cn-hangzhou", "r-kvstore.aliyuncs.com", "rds.aliyuncs.com", "杭州")
+    HONG_KONG = ("cn-hongkong", "r-kvstore.cn-hongkong.aliyuncs.com", "rds.aliyuncs.com", "香港")
+    SOUTHEAST = ("ap-southeast-1", "r-kvstore.ap-southeast-1.aliyuncs.com", "rds.ap-southeast-1.aliyuncs.com", "新加坡")
+    US_WEST = ("us-west-1", "r-kvstore.us-west-1.aliyuncs.com", "rds.us-west-1.aliyuncs.com", "美国(硅谷)")
 
-    def __init__(self, region, endpoint, vpc_endpoint, desc):
+    def __init__(self, region, kv_store_endpoint, rds_endpoint, desc):
         self.region = region
-        self.endpoint = endpoint
+        self.kv_store_endpoint = kv_store_endpoint
+        self.rds_endpoint = rds_endpoint
         self.desc = desc

+ 52 - 23
resource/kv_store_monitor.py

@@ -3,36 +3,65 @@ from datetime import datetime
 from resource.enums.region import Region
 from resource.service.FeiShuService import FeiShuService
 from resource.service.KVStoreService import KVStoreService
+from resource.utils.utils import value_covert, bottom_right, convert_storage_unit
 
 kv_store_service = KVStoreService()
 fei_shu_service = FeiShuService()
 
-# fei_shu_spread_sheet_token = "XzQGsheQzhk74rtknKacClASnTc"
-# fei_shu_sheet_id = "25dce4"
-#
-# tenant_access_token = fei_shu_service.get_tenant_access_token("cli_a89702999f3c900b", "47ewnaxRqJAvHYdUR8idHgfzfeqAu0Pz")
+fei_shu_spread_sheet_token = "XzQGsheQzhk74rtknKacClASnTc"
+fei_shu_sheet_id = "25dce4"
+
+tenant_access_token = fei_shu_service.get_tenant_access_token("cli_a89702999f3c900b", "47ewnaxRqJAvHYdUR8idHgfzfeqAu0Pz")
+
+charge_type_map = {
+    "PrePaid": "预付费",
+    "PostPaid": "后付费",
+}
+
+architecture_type_map = {
+    "cluster": "集群版",
+    "standard": "标准版",
+    "rwsplit": "读写分离版"
+}
+
+status_map = {
+    "Normal": "正常",
+    "Creating": "创建中",
+    "Changing": "修改中",
+    "Inactive": "被禁用",
+    "Flushing": "清除中",
+    "Released": "已释放",
+    "Transforming": "转换中",
+    "Unavailable": "服务停止",
+    "Error": "创建失败",
+    "Migrating": "迁移中",
+    "BackupRecovering": "备份恢复中",
+    "MinorVersionUpgrading": "小版本升级中",
+    "NetworkModifying": "网络变更中",
+    "SSLModifying": "SSL 变更中",
+    "MajorVersionUpgrading": "大版本升级中,可正常访问"
+}
 
 
 def main():
-    all_instances = kv_store_service.get_all_instance(region=Region.HANG_ZHOU)
-    for instance in all_instances:
-        dt = datetime.now().strftime('%Y-%m-%d')
-        instance_id = instance.instance_id  # 实例ID
-        instance_name = instance.instance_name  # 实例名
-        instance_status = instance.instance_status  # 实例状态
-        instance_type = instance.instance_type  # 实例类型
-        zone_id = instance.zone_id  # 可用区ID
-        architecture_type = instance.architecture_type  # 架构类型
-        engine_version = instance.engine_version  # 兼容的Redis版本
-        charge_type = instance.charge_type  # 付费类型
-        capacity = instance.capacity / 1024  # 容量
-        values = [[dt, zone_id, instance_id, instance_name, instance_status, instance_type, architecture_type, engine_version, charge_type, capacity]]
-
-        # fei_shu_service.spreadsheet_values_prepend(tenant_access_token, fei_shu_spread_sheet_token, f"{fei_shu_sheet_id}!A2:J2", values)
-        instance_attribute = kv_store_service.describe_instance_attribute(instance_id, Region.HANG_ZHOU)
-        storage = instance_attribute.storage
-        availability_value = instance_attribute.availability_value
-        print(f"{instance_id} --> {storage} --> {availability_value}")
+    region_list = [Region.HANG_ZHOU, Region.HONG_KONG, Region.US_WEST, Region.SOUTHEAST]
+    for region in region_list:
+        all_instances = kv_store_service.get_all_instance(region=region)
+        for instance in all_instances:
+            dt = datetime.now().strftime('%Y-%m-%d')
+            instance_id = instance.instance_id  # 实例ID
+            instance_name = instance.instance_name  # 实例名
+            instance_status = value_covert(instance.instance_status, status_map)  # 实例状态
+            instance_type = instance.instance_type  # 实例类型
+            zone_id = instance.zone_id  # 可用区ID
+            architecture_type = value_covert(instance.architecture_type, architecture_type_map)  # 架构类型
+            engine_version = instance.engine_version  # 兼容的Redis版本
+            charge_type = value_covert(instance.charge_type, charge_type_map)  # 付费类型
+            capacity = convert_storage_unit(instance.capacity, "mb", "gb")  # 容量
+            values = [[dt, region.desc, zone_id, instance_id, instance_name, instance_status, instance_type, architecture_type, engine_version, charge_type, capacity]]
+            start_col = "A2"
+            end_col = bottom_right(start_col, values)
+            fei_shu_service.spreadsheet_values_prepend(tenant_access_token, fei_shu_spread_sheet_token, f"{fei_shu_sheet_id}!{start_col}:{end_col}", values)
 
 
 if __name__ == "__main__":

+ 102 - 0
resource/rds_monitor.py

@@ -0,0 +1,102 @@
+from datetime import datetime
+
+from resource.enums.region import Region
+from resource.service.FeiShuService import FeiShuService
+from resource.utils.utils import value_covert, bottom_right, convert_storage_unit
+from service.RDSServicee import RDSService
+
+rds_service = RDSService()
+fei_shu_service = FeiShuService()
+
+fei_shu_spread_sheet_token = "XzQGsheQzhk74rtknKacClASnTc"
+fei_shu_sheet_id = "R50CB5"
+
+tenant_access_token = fei_shu_service.get_tenant_access_token("cli_a89702999f3c900b", "47ewnaxRqJAvHYdUR8idHgfzfeqAu0Pz")
+
+instance_type_map = {
+    "Primary": "主实例",
+    "Readonly": "只读实例",
+    "Guard": "灾备实例",
+    "Temp": "临时实例"
+}
+
+status_map = {
+    "Creating": "创建中",
+    "Running": "使用中",
+    "Deleting": "删除中",
+    "Rebooting": "重启中",
+    "Stopping": "暂停中",
+    "Stopped": "已暂停",
+    "DBInstanceClassChanging": "升降级中",
+    "TRANSING": "迁移中",
+    "EngineVersionUpgrading": "迁移版本中",
+    "TransingToOthers": "迁移数据到其他RDS中",
+    "GuardDBInstanceCreating": "生产灾备实例中",
+    "Restoring": "备份恢复中",
+    "Importing": "数据导入中",
+    "ImportingFromOthers": "从其他RDS实例导入数据中",
+    "DBInstanceNetTypeChanging": "内外网切换中",
+    "GuardSwitching": "容灾切换中",
+    "INS_CLONING": "实例克隆中",
+    "Released": "已释放实例"
+}
+
+storage_type_map = {
+    "local_ssd": "高性能本地盘",
+    "ephemeral_ssd": "性能本地盘",
+    "cloud_ssd": "SSD 云盘",
+    "general_essd": "高性能云盘",
+    "cloud_essd0": "ESSD PL0 云盘",
+    "cloud_essd": "ESSD PL1 云盘",
+    "cloud_essd2": "ESSD PL2 云盘",
+    "cloud_essd3": "ESSD PL3 云盘"
+}
+
+pay_type_map = {
+    "Postpaid": "按量付费",
+    "Prepaid": "包年包月",
+    "SERVERLESS": "Serverless"
+}
+
+category_map = {
+    "Basic": "基础系列",
+    "HighAvailability": "高可用系列",
+    "cluster": "MySQL 集群系列",
+    "AlwaysOn": "SQL Server 集群系列",
+    "Finance": "三节点企业系列",
+    "Serverless_basic": "Serverless 基础系列"
+}
+
+
+def main():
+    region_list = [Region.HANG_ZHOU, Region.HONG_KONG, Region.US_WEST, Region.SOUTHEAST]
+    for region in region_list:
+        all_instances = rds_service.get_all_instance(region=region)
+        for instance in all_instances:
+            dt = datetime.now().strftime('%Y-%m-%d')
+            instance_id = instance.dbinstance_id
+            instance_name = instance.dbinstance_description
+            category = value_covert(instance.category, category_map)
+            memory = convert_storage_unit(instance.dbinstance_memory, "mb", "gb")
+            cpu = instance.dbinstance_cpu
+            status = value_covert(instance.dbinstance_status, status_map)
+            instance_type = value_covert(instance.dbinstance_type, instance_type_map)
+            engine = instance.engine
+            engine_version = instance.engine_version
+            zone_id = instance.zone_id
+
+            attributes = rds_service.get_instance_detail(region, instance_id)
+            attribute = attributes[0]
+            disk_used = convert_storage_unit(attribute.dbinstance_disk_used, "b", "gb")
+            storage = attribute.dbinstance_storage
+            storage_type = value_covert(attribute.dbinstance_storage_type, storage_type_map)
+            pay_type = value_covert(attribute.pay_type, pay_type_map)
+
+            values = [[dt, region.desc, zone_id, instance_id, instance_name, category, status, instance_type, pay_type, engine, engine_version, memory, cpu, disk_used, storage, storage_type]]
+            start_col = "A2"
+            end_col = bottom_right(start_col, values)
+            fei_shu_service.spreadsheet_values_prepend(tenant_access_token, fei_shu_spread_sheet_token, f"{fei_shu_sheet_id}!{start_col}:{end_col}", values)
+
+
+if __name__ == "__main__":
+    main()

+ 17 - 4
resource/service/KVStoreService.py

@@ -1,5 +1,7 @@
+from datetime import datetime
 from typing import Optional
 
+import pytz
 from alibabacloud_r_kvstore20150101.client import Client as KVStoreClient
 from alibabacloud_r_kvstore20150101.models import *
 from alibabacloud_tea_openapi import models
@@ -10,14 +12,14 @@ from resource.enums.region import Region
 class KVStoreService(object):
     def __init__(self):
         self.region_client_map: Dict[Region, KVStoreClient] = {}
-        self.access_key_id = "LTAI4GBWbFvvXoXsSVBe1o9f"
-        self.access_key_secret = "kRAikWitb4kDxaAyBqNrmLmllMEDO3"
+        self.access_key_id = "LTAI5tRwjztCCwQNBB6nW1dY"
+        self.access_key_secret = "NaTnMxrGEJh64tLly7Kb5tr166Xpos"
 
     def create_client(self, region: Region) -> KVStoreClient:
         config = models.Config(
             access_key_id=self.access_key_id,
             access_key_secret=self.access_key_secret,
-            endpoint=region.endpoint,
+            endpoint=region.kv_store_endpoint,
         )
         return KVStoreClient(config)
 
@@ -45,7 +47,18 @@ class KVStoreService(object):
         client = self.get_kvstore_client(region)
         return client.describe_instances(request).body.instances
 
-    def describe_instance_attribute(self, instance_id: str, region: Region) -> Optional[DescribeInstanceAttributeResponseBodyInstancesDBInstanceAttribute]:
+    def describe_instance_attribute(self, instance_id: str, region: Region) -> Optional[List[DescribeInstanceAttributeResponseBodyInstancesDBInstanceAttribute]]:
         request = DescribeInstanceAttributeRequest(instance_id=instance_id)
         response = self.get_kvstore_client(region).describe_instance_attribute(request)
         return response.body.instances.dbinstance_attribute
+
+    def describe_history_monitor_values(self, instance_id: str, region: Region, start_time: datetime, end_time: datetime, monitor_keys: str) -> Optional[DescribeHistoryMonitorValuesResponseBody]:
+        request = DescribeHistoryMonitorValuesRequest(
+            instance_id=instance_id,
+            start_time=start_time.astimezone(pytz.UTC).strftime('%Y-%m-%dT%H:%M:%SZ'),
+            end_time=end_time.astimezone(pytz.UTC).strftime('%Y-%m-%dT%H:%M:%SZ'),
+            monitor_keys=monitor_keys,
+            interval_for_history="01m"
+        )
+        response = self.get_kvstore_client(region).describe_history_monitor_values(request)
+        return response.body

+ 68 - 0
resource/service/RDSServicee.py

@@ -0,0 +1,68 @@
+import uuid
+from typing import Dict, List, Tuple
+
+from alibabacloud_rds20140815.client import Client as RDSClient
+from alibabacloud_rds20140815.models import DescribeDBInstancesResponseBodyItemsDBInstance, DescribeDBInstancesRequest, DescribeDBInstanceAttributeRequest, \
+    DescribeDBInstanceAttributeResponseBodyItemsDBInstanceAttribute
+from alibabacloud_tea_openapi import models
+
+from resource.enums.region import Region
+
+
+class RDSService(object):
+    def __init__(self):
+        self.access_key_id = "LTAI5tRwjztCCwQNBB6nW1dY"
+        self.access_key_secret = "NaTnMxrGEJh64tLly7Kb5tr166Xpos"
+        self.region_client_map: Dict[Region, RDSClient] = {}
+
+    def create_client(self, region: Region) -> RDSClient:
+        config = models.Config(
+            access_key_id=self.access_key_id,
+            access_key_secret=self.access_key_secret,
+            endpoint=region.rds_endpoint
+        )
+        return RDSClient(config)
+
+    def get_rds_client(self, region: Region) -> RDSClient:
+        if region in self.region_client_map:
+            return self.region_client_map[region]
+
+        client = self.create_client(region)
+        self.region_client_map[region] = client
+        return client
+
+    def get_all_instance(self, region: Region) -> List[DescribeDBInstancesResponseBodyItemsDBInstance]:
+        page_number = 0
+        all_instances: List[DescribeDBInstancesResponseBodyItemsDBInstance] = []
+        while True:
+            next_token, instances = self.get_instances(region=region, page_number=page_number)
+            if not instances:
+                break
+            all_instances.extend(instances)
+            page_number += 1
+        return all_instances
+
+    def get_instances(self, region: Region, page_size: int = 100, page_number: int = 0, next_token: str = "") -> Tuple[str, List[DescribeDBInstancesResponseBodyItemsDBInstance]]:
+        client = self.get_rds_client(region)
+        request = DescribeDBInstancesRequest(
+            client_token=self.gen_request_id(),
+            page_size=page_size,
+            page_number=page_number,
+            region_id=region.region,
+            instance_level=1,
+        )
+        response = client.describe_dbinstances(request)
+        body = response.body
+        return body.next_token, body.items.dbinstance
+
+    def get_instance_detail(self, region: Region, instance_id: str) -> List[DescribeDBInstanceAttributeResponseBodyItemsDBInstanceAttribute]:
+        client = self.get_rds_client(region)
+        request = DescribeDBInstanceAttributeRequest(
+            dbinstance_id=instance_id
+        )
+        response = client.describe_dbinstance_attribute(request)
+        return response.body.items.dbinstance_attribute
+
+    @staticmethod
+    def gen_request_id():
+        return str(uuid.uuid4()).replace("-", "")

+ 0 - 0
resource/utils/__init__.py


+ 76 - 0
resource/utils/utils.py

@@ -0,0 +1,76 @@
+import re
+from typing import Dict
+
+
+def col_to_num(s):
+    n = 0
+    for c in s.upper():
+        n = n * 26 + ord(c) - 64
+    return n
+
+
+def num_to_col(n):
+    s = ""
+    while n:
+        n, r = divmod(n - 1, 26)
+        s = chr(r + 65) + s
+    return s
+
+
+def bottom_right(start_cell, data):
+    col, row = re.match(r"([A-Za-z]+)(\d+)", start_cell).groups()
+    rows = len(data)
+    cols = len(data[0]) if rows else 0
+
+    end_col = col_to_num(col) + cols - 1
+    end_row = int(row) + rows - 1
+
+    return f"{num_to_col(end_col)}{end_row}"
+
+
+def value_covert(value: str, map_dict: Dict[str, str]) -> str:
+    if not map_dict:
+        return value
+
+    if value not in map_dict:
+        return value
+
+    return f"{value}({map_dict[value]})"
+
+
+def convert_storage_unit(value, from_unit, to_unit):
+    """
+    存储单位换算核心函数
+    :param value: 要转换的数值(数字类型)
+    :param from_unit: 原始单位(如 B/KB/MB/GB/TB,不区分大小写)
+    :param to_unit: 目标单位(如 B/KB/MB/GB/TB,不区分大小写)
+    :return: 换算后的结果(保留2位小数)
+    """
+    # 定义单位与字节(B)的换算比例(1单位 = 多少B)
+    unit_map = {
+        'b': 1,
+        'kb': 1024,
+        'mb': 1024 ** 2,
+        'gb': 1024 ** 3,
+        'tb': 1024 ** 4
+    }
+
+    # 统一单位为小写,避免大小写问题
+    from_unit = from_unit.strip().lower()
+    to_unit = to_unit.strip().lower()
+
+    # 校验单位是否合法
+    if from_unit not in unit_map or to_unit not in unit_map:
+        raise ValueError(f"不支持的单位!仅支持:{list(unit_map.keys())}")
+
+    # 校验数值是否合法
+    try:
+        value = float(value)
+    except ValueError:
+        raise ValueError("数值必须是数字(如 1024、2.5 等)")
+
+    # 换算逻辑:先转为字节,再转为目标单位
+    bytes_value = value * unit_map[from_unit]  # 原始值转字节
+    result = bytes_value / unit_map[to_unit]  # 字节转目标单位
+
+    return round(result, 2)  # 保留2位小数,提升可读性