Technical Article

Monitor Database and Log Growth

,

Most DBA's would like to be notified when automatic database file growth occurs. This script will log file growth. It will not log file shrink but could be easily modifed to do so.

Script does the following:
1. create tables to store server list, current & historical file sizes and actual growth for each database file
2. add a user-defined error message to state that file size has changed
3. create an alert for that message that will notify an operator of file growth
4. create stored procedure to compare the current file size and the stored file size. If file size has changed this stored procedure will raise the error and it will trigger the alert.
5. create a scheduled job to run the procedure regularly.

/*
***************************************************************
***               monitor_file_growth                            ***
***               Created by:  Lori Brown                      ***
***               Date: 7/12/2005                                 ***
***************************************************************

Most DBA's would like to be notified when automatic database file growth occurs. 
This script will log file growth.  It will not log file shrink but could be easily modifed to do so.

Script does the following: 
1. create tables to store server list, current & historical file sizes and actual growth for each database file 
2. add a user-defined error message to state that file size has changed 
3. create an alert for that message that will notify an operator of file growth  
4. create stored procedure to compare the current file size and the stored file size. If file size has changed this stored procedure will raise the error and it will trigger the alert. 
5. create a scheduled job to run the procedure regularly. 

NOTES:   
** This is done from a single SQL Server that is used for administrative purposes.  You must add linked servers to all of the servers that you want to check.
** Populate the serverlist table with the names of the servers that you want to check.
** I have set the proc to clean out historical records more than 10 days old.  You can modify this parameter if you want.  
         There is really no reason to keep much historical data since growth is logged to the dbfile_growth_info table.
** In the procedure, the variable @xlist holds an exclusion list of databases that I do not want to check.  You can modify this list to your requirements.
** You can modify the job schedule to your requirements. I scheduled mine to run every 2 hours.
*/
--
--Replace string <operator_name> with the operator name
--Replace string <admindatabase> with the administrative database name 
--
USE <admindatabase>
GO
--serverlist table holds names of all servers that are checked
CREATE TABLE [dbo].[serverlist] (
[srvrname] [varchar] (30) NULL 
) ON [PRIMARY]
GO
--current_dbfile_info will hold the most recent file stats.  
--The contents of this table are first put into the historical_dbfile_info table and then truncated each time the proc is run.
CREATE TABLE [dbo].[current_dbfile_info] (
[servername] [varchar] (50) NULL ,
[dbname] [varchar] (50) NULL ,
[fileid] [int] NOT NULL ,
[statdate] [datetime] NULL ,
[size] [int] NULL ,
[sizeMB] [int] NULL ,
[filename] [varchar] (250) NULL 
) ON [PRIMARY]
GO
--historical_dbfile_info table will hold historical stats.
--The column mostrecentstat when set to 1 will indicate that the record is the most recent and is used to identify the record to compare against the current stat.
CREATE TABLE [dbo].[historical_dbfile_info] (
[statdate] [datetime] NOT NULL ,
[servername] [varchar] (50) NOT NULL ,
[dbname] [varchar] (50) NOT NULL ,
[fileid] [int] NOT NULL ,
[size] [int] NULL ,
[sizeMB] [int] NULL ,
[filename] [varchar] (250) NOT NULL ,
[mostrecentstat] [smallint] NOT NULL 
) ON [PRIMARY]
GO

ALTER TABLE [dbo].[historical_dbfile_info] ADD 
CONSTRAINT [DF_historical_dbfile_info_mostrecentstat] DEFAULT (0) FOR [mostrecentstat],
CONSTRAINT [PK_historical_dbfile_info] PRIMARY KEY  CLUSTERED 
(
[fileid],
[statdate],
[servername],
[dbname]
)  ON [PRIMARY] 
GO
--dbfile_growth_info table will hold only records of growth.
--this table is used for reporting and to monitor over time when file growth occurred
CREATE TABLE [dbo].[dbfile_growth_info] (
[statdate] [datetime] NOT NULL ,
[servername] [varchar] (50) NOT NULL ,
[dbname] [varchar] (50) NOT NULL ,
[filename] [varchar] (250) NOT NULL ,
[sizeMBnow] [int] NULL ,
[sizeMBwas] [int] NULL 
) ON [PRIMARY]
GO

ALTER TABLE [dbo].[dbfile_growth_info] ADD 
CONSTRAINT [PK_dbfile_growth_info] PRIMARY KEY  CLUSTERED 
(
[statdate],
[servername],
[dbname],
[filename]
)  ON [PRIMARY] 
GO

--add a user-defined error message to state that file size has changed
EXEC sp_addmessage @msgnum = 60000, @severity = 16, 
    @msgtext = N'A database file has auto grown. Check dbfile_growth_xstatdt or dbfile_growth_xdb for information.',
    @lang = 'us_english',
      @replace = REPLACE
GO
--create an alert for that message. This alert will send email to an operator.
IF (EXISTS (SELECT name FROM msdb.dbo.sysalerts WHERE name = N'Database file has changed size'))
 ---- Delete the alert with the same name if one already exists.
  EXECUTE msdb.dbo.sp_delete_alert @name = N'Database file has changed size' 
BEGIN 
--Add alert
EXECUTE msdb.dbo.sp_add_alert @name = N'Database file has changed size', @message_id = 60000,
@severity = 0, @enabled = 1, @delay_between_responses = 0, @include_event_description_in = 5, 
@category_name = N'[Uncategorized]'
--notify operator
EXECUTE msdb.dbo.sp_add_notification @alert_name = N'Database file has changed size', 
@operator_name = N'<operator_name>', @notification_method = 1
END
GO

--view to see growth by most recent statdate
CREATE VIEW dbo.dbfile_growth_xstatdt
AS
SELECT TOP 20 statdate, servername, dbname, filename, sizeMBnow, sizeMBwas 
FROM dbo.dbfile_growth_info
ORDER BY statdate DESC, servername ASC, dbname ASC
GO

--view to see growth grouped by db
CREATE VIEW dbo.dbfile_growth_xdb
AS
SELECT TOP 20 statdate, servername, dbname, filename, sizeMBnow, sizeMBwas 
FROM dbo.dbfile_growth_info
ORDER BY servername ASC, dbname ASC, statdate DESC
GO

--Create the procedure
CREATE PROCEDURE dbo.monitor_file_growth
AS
SET NOCOUNT ON
--set mostrecentstat flag to no in historical table
UPDATE dbo.historical_dbfile_info SET mostrecentstat = 0 WHERE mostrecentstat = 1
--move current to historical table with mostrecentstat flag set to yes
INSERT INTO dbo.historical_dbfile_info (statdate,servername,dbname,fileid,[size],sizeMB,filename,mostrecentstat)
   (SELECT statdate,servername,dbname,fileid,[size],sizeMB,filename,1 FROM dbo.current_dbfile_info)
--only keep 10 days of historical data
DELETE FROM dbo.historical_dbfile_info WHERE statdate < getdate()-10 
--get rid of old current data
TRUNCATE TABLE dbo.current_dbfile_info
--declare variables
DECLARE @SQLString NVARCHAR(500)
DECLARE @SQLStr1 NVARCHAR(500)
DECLARE @SQLStr2 NVARCHAR(500)
DECLARE @xlist VARCHAR(100)      --db exclusion list
DECLARE @sn VARCHAR(32)          
DECLARE @dbn VARCHAR(32)
--list of servers cursor
DECLARE listservers CURSOR FOR
SELECT srvrname FROM dbo.serverlist
--open server cursor
OPEN listservers
--get first server to process
FETCH next FROM listservers INTO @sn
--begin server loop
WHILE (@@FETCH_STATUS = 0)
BEGIN
   --run for each database on the server
   --Declare cursor for holding the names of all databases on the server
   SET @xlist = '''master'',''model'',''msdb'',''distribution'',''Northwind'',''pubs'',''tempdb''' 
   SET @SQLStr1 = 'DECLARE listdbs CURSOR FOR SELECT name FROM ['+@sn+'].master.dbo.sysdatabases WHERE name NOT IN('+@xlist+')'
   EXEC sp_executesql @SQLStr1
   --open database cursor
   OPEN listdbs
   --get first database to process
   FETCH NEXT FROM listdbs INTO @dbn
   --begin database loop
   WHILE (@@FETCH_STATUS = 0)
   BEGIN
      --add new current data
      SET @SQLStr2 = 'INSERT INTO current_dbfile_info SELECT '''+@sn+''', '''+@dbn+''', fileid, getdate(), size, ((size*8)/1024), filename FROM ['+@sn+'].['+@dbn+'].dbo.sysfiles'
      EXEC sp_executesql @SQLStr2
      -- Get next database to process
      FETCH NEXT FROM listdbs INTO @dbn
   END  --end database loop
   --clean up database cursor
   CLOSE listdbs
   DEALLOCATE listdbs
   --get next server to process
   FETCH next FROM listservers INTO @sn
END  --end server loop
--clean up server cursor
CLOSE listservers
DEALLOCATE listservers
-- compare the current size against the stored file size  
--if the current size is larget than the historical size then it is logged into dbfile_growth_info table for reporting and error is raised that will alert the DBA of the file growth
IF EXISTS (SELECT 1 FROM dbo.current_dbfile_info c JOIN dbo.historical_dbfile_info h 
ON (c.servername = h.servername AND c.dbname = h.dbname AND c.fileid = h.fileid)
WHERE h.mostrecentstat = 1 AND (c.sizeMB > h.sizeMB))
BEGIN
   INSERT INTO dbo.dbfile_growth_info 
      SELECT c.statdate, c.servername, c.dbname, c.filename, c.sizeMB, h.sizeMB FROM dbo.current_dbfile_info c JOIN dbo.historical_dbfile_info h 
      ON (c.servername = h.servername AND c.dbname = h.dbname AND c.fileid = h.fileid)
         WHERE h.mostrecentstat = 1 AND (c.sizeMB > h.sizeMB)
         ORDER BY c.statdate, c.servername, c.dbname, c.filename

   RAISERROR(60000, 16, 1) WITH LOG
END
GO

--Create the job to run the proc
BEGIN TRANSACTION            
  DECLARE @JobID BINARY(16)  
  DECLARE @ReturnCode INT    
  SELECT @ReturnCode = 0     
IF (SELECT COUNT(*) FROM msdb.dbo.syscategories WHERE name = N'AdminJobs') < 1 
  EXECUTE msdb.dbo.sp_add_category @name = N'AdminJobs'

  -- Delete the job with the same name (if it exists)
  SELECT @JobID = job_id     
  FROM   msdb.dbo.sysjobs    
  WHERE (name = N'Monitor File Growth')       
  IF (@JobID IS NOT NULL)    
  BEGIN  
  -- Check if the job is a multi-server job  
  IF (EXISTS (SELECT  * 
              FROM    msdb.dbo.sysjobservers 
              WHERE   (job_id = @JobID) AND (server_id <> 0))) 
  BEGIN 
    -- There is, so abort the script 
    RAISERROR (N'Unable to import job ''Monitor File Growth'' since there is already a multi-server job with this name.', 16, 1) 
    GOTO QuitWithRollback  
  END 
  ELSE 
    -- Delete the [local] job 
    EXECUTE msdb.dbo.sp_delete_job @job_name = N'Monitor File Growth' 
    SELECT @JobID = NULL
  END 

BEGIN 

  -- Add the job
  EXECUTE @ReturnCode = msdb.dbo.sp_add_job @job_id = @JobID OUTPUT , @job_name = N'Monitor File Growth', @owner_login_name = N'sa', @description = N'Checks file size for all db''s on all servers and will send notification if the size changes.', @category_name = N'AdminJobs', @enabled = 1, @notify_level_email = 2, @notify_level_page = 0, @notify_level_netsend = 0, @notify_level_eventlog = 2, @delete_level= 0, @notify_email_operator_name = N'<operator_name>'
  IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback 

  -- Add the job steps
  EXECUTE @ReturnCode = msdb.dbo.sp_add_jobstep @job_id = @JobID, @step_id = 1, @step_name = N'Check db file size', @command = N'EXEC dbo.monitor_file_growth', @database_name = N'<admindatabase>', @server = N'', @database_user_name = N'', @subsystem = N'TSQL', @cmdexec_success_code = 0, @flags = 0, @retry_attempts = 0, @retry_interval = 1, @output_file_name = N'', @on_success_step_id = 0, @on_success_action = 1, @on_fail_step_id = 0, @on_fail_action = 2
  IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback 
  EXECUTE @ReturnCode = msdb.dbo.sp_update_job @job_id = @JobID, @start_step_id = 1 

  IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback 

  -- Add the job schedules
  EXECUTE @ReturnCode = msdb.dbo.sp_add_jobschedule @job_id = @JobID, @name = N'Check db file growth schedule', @enabled = 1, @freq_type = 4, @active_start_date = 20050711, @active_start_time = 0, @freq_interval = 1, @freq_subday_type = 8, @freq_subday_interval = 2, @freq_relative_interval = 0, @freq_recurrence_factor = 0, @active_end_date = 99991231, @active_end_time = 235959
  IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback 

  -- Add the Target Servers
  EXECUTE @ReturnCode = msdb.dbo.sp_add_jobserver @job_id = @JobID, @server_name = N'(local)' 
  IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback 

END
COMMIT TRANSACTION          
GOTO   EndSave              
QuitWithRollback:
  IF (@@TRANCOUNT > 0) ROLLBACK TRANSACTION 
EndSave:

Rate

5 (1)

You rated this post out of 5. Change rating

Share

Share

Rate

5 (1)

You rated this post out of 5. Change rating