• Python监控SQL Server数据库服务器磁盘使用情况


     

    本篇博客总结一下Python采集SQL Server数据库服务器的磁盘使用信息,其实这里也是根据需求不断推进演化的一个历程,我们监控服务器的磁盘走了大概这样一个历程:

     

     

    1:使用SQL Server作业监控磁盘空间

     

        很久之前写过一篇博客MS SQL 监控磁盘空间告警,后面对这个脚本进行过多次完善和优化,做成了一个模板。在每台SQL Server服务器上都部署了,确实也很实用。告警也很给力,但是缺点也非常明显。

     

    优点:1: 自己动手DIY,在没有部署运维工具的前提下,确实能提前预警,抛开不足来说,告警还是非常给力的。

     

    缺点: 1: 每台服务器都需要部署,升级也很是麻烦。

         

          2: 数据分散,在模式上有致命的先天不足。监控工具一般为星型结构,采集集中数据。

     

            每台服务器都需要部署,如果有修改,每台服务器都需要发布更新,维护管理不方便。采集的一些数据分散,每台SQL Server数据库都需要保存一点数据。

     

          3: 通用性差,只能监控SQL Server服务器,Linux服务器,我们用的是crontab跑shell+perl脚本来监控磁盘空间并告警。

     

     

    2: Zabbix监控磁盘告警

     

     

    后面部署了Zabbix监控工具,Zabbix监控工具功能强大,不仅仅提供了磁盘空间告警功能,还提供了监控磁盘I/O等功能。更重要的是通用性很强大:只要是服务器就能监控,而方法1仅仅能监控SQL Server数据库服务器。

     

    优点: 1: Zabbix监控工具功能强大

        2: Zabbix监控工具通用性强

     

     

    缺点: 1:需要分析磁盘空间的历史数据比较麻烦,二次开发也比较麻烦(个人对zabbix了解不深入,可能对于高手而言,也是非常容易简单的事情,仅仅是个人的一点体会感受)。例如我要获取某个服务器的历史数据,对磁盘的增长情况做分析。需要关联好多表,非常麻烦。简单研究后就直接放弃了。

     

    自从Zabbix监控工具上线后,方法1就显得可有可无。基本上处于被替换的尴尬境地。

     

     

     

    3:使用Python脚本采集

     

    这里存粹是为了扩展我自己的工具MyDB的功能,顺手写点Python脚本练手,而且目前而言,只能采集SQL Server服务器的磁盘空间使用情况。功能和通用性不能和Zabbix监控工具比。功能有很多局限性和不足之处,通用性也很差,但是也有一些不错的优点。

     

    优点:  1:不需要部署客户端,也不需要每台服务器去部署(与方法1对比而言)。

         2:批量采集,集中保存采集数据。方便统一告警,数据分析。

         3:简洁与简单,灵活性高:目前就2个表,一个表[dbo].[SERVER_DISK_INFO]保存最近一次采集的磁盘空间使用情况数据,另外一个表[dbo].[SERVER_DISK_INFO_HIS]保存历史数据,可以做一些扩容分析等。

             例如,磁盘空间告警了,系统管理员会咨询我,如果进行扩容,需要多大的空间,保证半年内,不会再次出现告警,那么就可以一个脚本计算一下(平均每个月增长值* 月数)

     

    缺点:  1:通用性差,目前只能采集SQL Server数据库服务器的磁盘信息。后续再考虑扩展性。实在没有精力一步到位,慢慢完善扩充。

         2:功能单一,不像Zabbix,还可以监控磁盘I/O,这个Python脚本就只能采集磁盘空间使用率,并不能扩展其功能,有一定局限性。

     

     

     

    脚本get_win_disk_info.py如下所示:

    # -*- coding: utf-8 -*-
    '''
    -------------------------------------------------------------------------------------------
    --  Script Name             :   get_win_disk_info.py
    --  Script Auotor           :   潇湘隐者
    --  Script Description      :   采集SQL Server数据库的磁盘使用数据,方便统一分析和告警处理!
    -------------------------------------------------------------------------------------------
    '''
    import pymssql
    import logging
    import os.path
    import base64
    from cryptography.fernet import Fernet
     
     
    # 第一步,创建一个logger
    logger = logging.getLogger()
    logger.setLevel(logging.DEBUG)  # Log等级开关
    # 第二步,创建一个handler,用于写入日志文件
    #log_path = os.path.dirname(os.getcwd()) + '/logs/'
    log_path = '/home/konglb/logs/'
    log_name = log_path + 'get_win_disk_info.log'
    logfile = log_name
    file_handler = logging.FileHandler(logfile, mode='a+')
    file_handler.setLevel(logging.ERROR)  # 输出到file的log等级的开关
    # 第三步,定义handler的输出格式
    formatter = logging.Formatter("%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s")
    file_handler.setFormatter(formatter)
    # 第四步,将handler添加到logger里面
    logger.addHandler(file_handler)
    # 如果需要同時需要在終端上輸出,定義一個streamHandler
    print_handler = logging.StreamHandler()  # 往屏幕上输出
    print_handler.setFormatter(formatter)  # 设置屏幕上显示的格式
    logger.addHandler(print_handler)
     
     
    key=bytes(os.environ.get('key'),encoding="utf8")
     
    cipher_suite = Fernet(key)
    with open('/home/konglb/python/conf/ms_db_conf.bin', 'rb') as file_object:
        for line in file_object:
            encryptedpwd = line
    decrypt_pwd = (cipher_suite.decrypt(encryptedpwd))
    password_decrypted = bytes(decrypt_pwd).decode("utf-8") #convert to string
    env_db_user=os.environ.get('db_user')
    db_user=base64.b64decode(bytes(env_db_user, encoding="utf8"))
     
     
    try:
        dest_db_conn = pymssql.connect(host=os.environ.get('db_host'),
                                  user=bytes.decode(db_user),
                                  password=password_decrypted,
                                  database='DATABASE_REPOSITORY',
                                  charset="utf8");key=bytes(os.environ.get('key'),encoding="utf8")
     
     
     
        # cursor = dest_db_conn.cursor();
        # as_dict(bool) :如果设置为True,则后面的查询结果返回的是字典,关键字为查询结果的列名;否则(默认)返回的为list。
        # 可以通过在创建游标时指定as_dict参数来使游标返回字典变量,字典中的键为数据表的列名
        cursor = dest_db_conn.cursor(as_dict=True)
        #DELETE FROM [dbo].[DB_JOB_RUN_ERROR]
        #  WHERE  RUN_DATE_TIME >= CAST(CONVERT(VARCHAR(10),GETDATE(),120) AS DATETIME);
        sql_text = """INSERT INTO dbo.SERVER_DISK_INFO_HIS
                              ( COLLECT_DATE ,
                                FACTORY_CD ,
                                SERVER_NAME ,              
                                DISK_NAME ,
                                TOTAL_SPACE ,
                                USED_SPACE ,
                                FREE_SPACE ,
                                FREE_PERCENT
                              )
                      SELECT  COLLECT_DATE ,
                              FACTORY_CD ,
                              SERVER_NAME ,
                              DISK_NAME ,
                              TOTAL_SPACE ,
                              USED_SPACE ,
                              FREE_SPACE ,
                              FREE_PERCENT
                      FROM    [dbo].[SERVER_DISK_INFO];
                      
                      TRUNCATE TABLE  [dbo].[SERVER_DISK_INFO];
                    """
        cursor.execute(sql_text);
        dest_db_conn.commit()
     
        sql_text = """
                  SELECT  SERVER_CD ,
                          SERVER_IP ,
                          USER_NAME ,
                          dbo.DecryptByPassPhrasePwd(PASSWORD) AS PASSWORD ,
                          SERVER_NAME,
                          DB_VERSION ,
                          INSTANCE_NAME
                  FROM   dbo.DB_SERVER_CONFIG
                  WHERE  DATABASE_TYPE = 'SQL SERVER'
                          AND COLLECT_DATA = 1;
                """
     
        cursor.execute(sql_text);
        rows = cursor.fetchall();
     
        for row in rows:
     
            try:
                src_db_conn = pymssql.connect(host=row['SERVER_IP'],
                                              user=row['USER_NAME'],
                                              password=row['PASSWORD'],
                                              database='master',
                                              charset="utf8",
                                              autocommit=True);
     
                sub_cursor = src_db_conn.cursor(as_dict=True)
     
                if row['DB_VERSION'] <= 2000:
                    logger.info(row['SERVER_NAME'] + ' not gather')
                    continue
                else:
                    #logger.info(row['DB_VERSION'])
                    sql_db_patch="SELECT SERVERPROPERTY('productlevel') AS  PRODUCT_LEVEL"
                    sub_cursor.execute(sql_db_patch)
                    db_patch = sub_cursor.fetchone()
                    #必须转换,否则返回的为bytes,不是str
                    patch_info=  str(db_patch['PRODUCT_LEVEL'], encoding = "utf-8")
     
     
     
     
                  
                    if ((row['DB_VERSION']== 2005) or (row['DB_VERSION'] ==2008  and patch_info == 'RTM')):
                        #logger.info(row['SERVER_NAME'] + ' patch is ' + patch_info)
                        #continue
     
     
                        sql_job_info="""
                                        DECLARE @Result            INT;
                                        DECLARE @objectInfo        INT;
                                        DECLARE @DriveInfo        CHAR(1);
                                        DECLARE @TotalSize        VARCHAR(20);
                                        DECLARE @OutDrive        INT;
                                        DECLARE @UnitGB            FLOAT; 
                                        DECLARE @CurrentDate    DATETIME;
                                        DECLARE @ConfValue        INT;
                                        SET @UnitGB = 1073741824.0;
                                        
                                        
                                        --创建临时表保存服务器磁盘容量信息
                                        CREATE TABLE #DiskCapacity
                                        (
                                            COLLECT_DATE    DATETIME    ,
                                            FACTORY_CD      NVARCHAR(24),
                                            SERVER_NAME        NVARCHAR(64),
                                            DISK_NAME        NVARCHAR(2) ,
                                            TOTAL_SPACE        FLOAT,
                                            USED_SPACE        FLOAT,
                                            FREE_SPACE        FLOAT,
                                            FREE_PERCENT    FLOAT    
                                        );
                                        
                                        INSERT #DiskCapacity
                                                (DISK_NAME,FREE_SPACE ) 
                                        EXEC master.dbo.xp_fixeddrives;
                                         
                                        EXEC sp_configure 'show advanced options', 1
                                        RECONFIGURE WITH OVERRIDE;
                                        
                                        SELECT @ConfValue = value FROM sys.sysconfigures WHERE comment LIKE '%Ole Automation Procedures%'
                                        IF @ConfValue = 0 
                                        BEGIN
                                            EXEC sp_configure 'Ole Automation Procedures', 1;
                                            RECONFIGURE WITH OVERRIDE;
                                        END
                                        
                                        
                                        EXEC @Result = master.sys.sp_OACreate 'Scripting.FileSystemObject',@objectInfo OUT;
                                        
                                        DECLARE CR_DiskInfo CURSOR LOCAL FAST_FORWARD
                                        FOR SELECT  DISK_NAME FROM #DiskCapacity
                                        ORDER by DISK_NAME
                                        
                                        OPEN CR_DiskInfo;
                                        
                                        
                                        SET @CurrentDate = GETDATE();
                                        FETCH NEXT FROM CR_DiskInfo INTO @DriveInfo
                                        
                                        WHILE @@FETCH_STATUS=0
                                        BEGIN
                                        
                                            EXEC @Result = sp_OAMethod @objectInfo,'GetDrive', @OutDrive OUT, @DriveInfo
                                        
                                        
                                            EXEC @Result = sp_OAGetProperty @OutDrive,'TotalSize', @TotalSize OUT
                                        
                                        
                                            UPDATE #DiskCapacity
                                            SET TOTAL_SPACE=ROUND(@TotalSize/@UnitGB,2), COLLECT_DATE=@CurrentDate,
                                                FACTORY_CD=%s, SERVER_NAME=@@SERVERNAME,
                                                --USED_SPACE=(@TotalSize-FREE_SPACE)/@UnitGB,
                                                --FREE_PERCENT=FREE_SPACE/@TotalSize*100,
                                                FREE_SPACE=ROUND(FREE_SPACE/1024,2)
                                            WHERE DISK_NAME =@DriveInfo
                                        
                                            UPDATE #DiskCapacity
                                            SET USED_SPACE=(TOTAL_SPACE-FREE_SPACE),
                                                FREE_PERCENT=ROUND(FREE_SPACE/TOTAL_SPACE*100,2)
                                            WHERE DISK_NAME =@DriveInfo
                                        
                                            FETCH NEXT FROM CR_DiskInfo INTO @DriveInfo
                                        
                                        END
                                        
                                        CLOSE CR_DiskInfo
                                        DEALLOCATE CR_DiskInfo;
                                        
                                        EXEC @Result=sp_OADestroy @objectInfo
                                        
                                        EXEC sp_configure 'show advanced options', 1
                                        RECONFIGURE WITH OVERRIDE;
                                        
                                        IF @ConfValue = 0 
                                        BEGIN
                                            EXEC sp_configure 'Ole Automation Procedures', 0;
                                            RECONFIGURE WITH OVERRIDE;
                                        END
                                        
                                        
                                        EXEC sp_configure 'show advanced options', 0
                                        RECONFIGURE WITH OVERRIDE;
                                        SELECT * FROM #DiskCapacity                              
                                     """
                        sub_cursor.execute(sql_job_info, row['SERVER_CD']);
                        job_rows = sub_cursor.fetchall();
                        src_db_conn.close()
                        error_job_info = []
                        for sub_row in job_rows:
                            data = (
                            sub_row['COLLECT_DATE'], sub_row['FACTORY_CD'], sub_row['SERVER_NAME'], sub_row['DISK_NAME'],
                            sub_row['TOTAL_SPACE'], sub_row['USED_SPACE'], sub_row['FREE_SPACE'], sub_row['FREE_PERCENT'])
                            error_job_info.append(data)
     
     
                        '''
                        logger.info('2008 2005')
                        logger.info(row['SERVER_NAME'])
                        sub_cursor.callproc('[msdb].[dbo].[sp_get_diskinfo]')
                        result_rows =sub_cursor.fetchall()
                        src_db_conn.close()
                        error_job_info = []
                        for sub_row in result_rows:
                            data = (
                            sub_row['COLLECT_DATE'], sub_row['FACTORY_CD'], sub_row['SERVER_NAME'], sub_row['DISK_NAME'],
                            sub_row['TOTAL_SPACE'], sub_row['USED_SPACE'], sub_row['FREE_SPACE'], sub_row['FREE_PERCENT'])
                            error_job_info.append(data)
                        '''
                    else:
     
     
                        sql_job_info = """WITH Server_Disk AS 
                                          (
                                                  SELECT DISTINCT
                                                      REPLACE(vs.volume_mount_point, ':\' , '') AS DISK_NAME,
                                                      CAST(VS.total_bytes/1024.0/1024/1024 AS NUMERIC(18,2) ) AS [TOTAL_SPACE],
                                                      CAST(VS.available_bytes/1024.0/1024/1024  AS NUMERIC(18,2)) AS [FREE_SPACE]
                                                  FROM  sys.master_files AS f
                                                  CROSS APPLY sys.dm_os_volume_stats(f.database_id, f.file_id) AS vs
                                          )
                                          SELECT  GETDATE()         AS COLLECT_DATE,
                                                %s                     AS FACTORY_CD  ,
                                                  @@SERVERNAME        AS SERVER_NAME ,
                                                  D.DISK_NAME            AS DISK_NAME,
                                                  D.[TOTAL_SPACE]    AS TOTAL_SPACE,
                                                  D.[TOTAL_SPACE] - D.[FREE_SPACE]  
                                                                      AS USED_SPACE,
                                                  D.[FREE_SPACE]    AS FREE_SPACE,
                                                  CAST(D.[FREE_SPACE] * 100 / D.[TOTAL_SPACE] AS NUMERIC(18, 2)) AS FREE_PERCENT
                                          FROM    Server_Disk AS D
                                          ORDER BY D.DISK_NAME;
                                       """
     
                        sub_cursor.execute(sql_job_info, row['SERVER_CD']);
                        job_rows = sub_cursor.fetchall();
                        src_db_conn.close()
                        error_job_info = []
                        for sub_row in job_rows:
                            data = (sub_row['COLLECT_DATE'], sub_row['FACTORY_CD'], sub_row['SERVER_NAME'], sub_row['DISK_NAME'],
                                    sub_row['TOTAL_SPACE'], sub_row['USED_SPACE'], sub_row['FREE_SPACE'], sub_row['FREE_PERCENT'])
                            error_job_info.append(data)
     
                    save_job_info = """                                    
                                     INSERT  INTO dbo.SERVER_DISK_INFO
                                                 (   COLLECT_DATE
                                                   , FACTORY_CD
                                                   , SERVER_NAME
                                                   , DISK_NAME
                                                   , TOTAL_SPACE
                                                   , USED_SPACE
                                                   , FREE_SPACE
                                                   , FREE_PERCENT
                                                 )
                                     VALUES(%s,%s,%s,%s,%d,%d,%d,%d)"""
                    cursor.executemany(save_job_info, error_job_info);
                    dest_db_conn.commit()
                    logger.info(row['SERVER_NAME'] + ' gather successful')
     
     
            except  pymssql.InterfaceError as fe:
                logger.error(fe.message)
            except  pymssql.DatabaseError as e:
                dest_db_conn.rollback();
                logger.error(row['SERVER_IP'] + ' 采集出错,请检查处理异常')
                logger.error(e)
            finally:
                src_db_conn.close()
    except pymssql.InterfaceError as fe:
        logger.error(fe.message)
    except  pymssql.DatabaseError as e:
        dest_db_conn.rollback();
     
        logger.error(row['SERVER_IP'] + ' 采集出错,请检查处理异常')
        logger.error(e)
    finally:
        dest_db_conn.close()
  • 相关阅读:
    微信成为开发者C#代码
    Ajax.ActionLink()方法的使用
    Entity FrameWork初始化数据库的四种策略
    最长公共子序列
    表达式求值
    韩信点兵
    蛇形填数
    5个数求最值
    求转置矩阵问题
    素数求和问题
  • 原文地址:https://www.cnblogs.com/kerrycode/p/11485046.html
Copyright © 2020-2023  润新知