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

系统 1787 0

 

本篇博客总结一下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()
            

更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论