Python監控SQL Server數據庫服務器磁盤使用狀況

 

本篇博客總結一下Python採集SQL Server數據庫服務器的磁盤使用信息,其實這裏也是根據需求不斷推動演化的一個歷程,咱們監控服務器的磁盤走了大概這樣一個歷程:html

 

 

1:使用SQL Server做業監控磁盤空間python

 

    好久以前寫過一篇博客MS SQL 監控磁盤空間告警,後面對這個腳本進行過屢次完善和優化,作成了一個模板。在每臺SQL Server服務器上都部署了,確實也很實用。告警也很給力,可是缺點也很是明顯。sql

 

優勢:1: 本身動手DIY,在沒有部署運維工具的前提下,確實能提早預警,拋開不足來講,告警仍是很是給力的。shell

 

缺點: 1: 每臺服務器都須要部署,升級也非常麻煩。數據庫

     

      2: 數據分散,在模式上有致命的先天不足。監控工具通常爲星型結構,採集集中數據。服務器

 

        每臺服務器都須要部署,若是有修改,每臺服務器都須要發佈更新,維護管理不方便。採集的一些數據分散,每臺SQL Server數據庫都須要保存一點數據。app

 

      3: 通用性差,只能監控SQL Server服務器,Linux服務器,咱們用的是crontab跑shell+perl腳原本監控磁盤空間並告警。運維

 

 

2: Zabbix監控磁盤告警工具

 

 

後面部署了Zabbix監控工具,Zabbix監控工具功能強大,不只僅提供了磁盤空間告警功能,還提供了監控磁盤I/O等功能。更重要的是通用性很強大:只要是服務器就能監控,而方法1僅僅能監控SQL Server數據庫服務器。fetch

 

優勢: 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()
相關文章
相關標籤/搜索