17 June 2009

Performance Dashboard Main Report Quirkiness

I was attempting to view the performance dashboard main report today and ran across an interesting "quirk".


If your server has any connections that are more than 24.8 days old, the report will not run. Why 24.8 days? That is how many milliseconds it takes to overflow the datediff(ms,,) function – 24.855134837962962 days gets you a bit closer, but is unlikely to yield much additional information J The system in question had connections nearing two months old. It didn't get it's monthly reboot last month and hasn't failed over.


This bug is in the msdb.MS_PerfDashboard.usp_Main_GetSessionInfo stored procedure. The fix – short of occasionally restarting your SQL service—is to change the total_elapsed_time calculation:

------------------------------------------------------------------------------

--Changed per msdn forum

--FROM

--sum(convert(bigint, datediff(ms, login_time, getdate()))) - sum(convert(bigint, s.total_elapsed_time)) as idle_connection_time,

--TO

sum(convert(bigint, CAST ( DATEDIFF ( minute, login_time, getdate()) As BIGINT)*60000 + DATEDIFF ( millisecond, DATEADD ( minute,DATEDIFF ( minute, login_time, getdate() ), login_time ),getdate() ))) - sum(convert(bigint, s.total_elapsed_time)) as idle_connection_time,

-------------------------------------------------------------------------------

10 June 2009

Why use SQL 2008 client tools

Here’s a quick example of a time saving query analysis feature with SSMS 2008.

We want to examine the performance of a query. When we run the query, and ask for the actual execution plan, SSMS 2008 will also display missing indexes that the server thinks would speed the query (this example is estimated to have a 64% impact on the query):


Just right click on the “missing” index (in green) and choose “Missing Index Details…”


16 March 2009

SQL Agent Job Summary

This is a bit long and clunky, but it gives some quick insight into the SQL Agent jobs that run on a server.


-- Summary of SQL Agent jobs

-- job name, typical start time, average duration, average job runs per day, last run date/time, enabled

 

USE msdb

 

DECLARE @job_user sysname

SET @job_user = '[domain]\[account]'

SET @job_user = NULL

 

SELECT

   src2.name AS job_name,

  

   -- -- if a job runs more than once per day, its average start time tends to cluster near mid-day

   -- -- and become a useless measure, so we'll use NULL for these cases

   CASE WHEN src5.avg_runs_per_day = 1 THEN

      RIGHT('00' + CAST(((AVG(src2.start_time_in_seconds)/60)/60) % 60 AS VARCHAR), 2) + ':' +

      RIGHT('00' + CAST((AVG(src2.start_time_in_seconds)/60) % 60 AS VARCHAR), 2) + ':' +

      RIGHT('00' + CAST(AVG(src2.start_time_in_seconds) % 60 AS VARCHAR), 2)

   ELSE

      NULL

   END  AS typical_start_time,

  

   RIGHT('00' + CAST(((AVG(src2.duration_in_seconds)/60)/60) % 60 AS VARCHAR), 2) + ':' +

   RIGHT('00' + CAST((AVG(src2.duration_in_seconds)/60) % 60 AS VARCHAR), 2) + ':' +

   RIGHT('00' + CAST(AVG(src2.duration_in_seconds) % 60 AS VARCHAR), 2)  AS avg_duration,

  

   src5.avg_runs_per_day,

  

  CONVERT(VARCHAR, MAX(src2.run_date_time), 120) AS last_run_date_time,

 

  src6.enabled

     

FROM

 

-- -- get our average start time and average duration by job name

(

   SELECT

      CAST(SUBSTRING(convert(VARCHAR, run_date_time, 121), 12, 2) AS INT) * 60 * 60 +

      CAST(SUBSTRING(convert(VARCHAR, run_date_time, 121), 15, 2) AS INT) * 60 +

      CAST(SUBSTRING(convert(VARCHAR, run_date_time, 121), 18, 2) AS INT) AS start_time_in_seconds,

 

      CAST(SUBSTRING(run_duration, 1, 2) AS INT) * 60 * 60 +

      CAST(SUBSTRING(run_duration, 4, 2) AS INT) * 60 +

      CAST(SUBSTRING(run_duration, 7, 2) AS INT) AS duration_in_seconds,

     

      run_date_time,

     

      name

   FROM

   (

      SELECT

         sj.NAME,

        

         cast(  

         SUBSTRING(CAST(run_date AS VARCHAR), 1, 4) + '-' +

         SUBSTRING(CAST(run_date AS VARCHAR), 5, 2) + '-' +

         SUBSTRING(CAST(run_date AS VARCHAR), 7, 2) + ' ' +

         SUBSTRING(RIGHT('00000' + CAST(run_time AS VARCHAR), 6), 1, 2) + ':' +

         SUBSTRING(RIGHT('00000' + CAST(run_time AS VARCHAR), 6), 3, 2) + ':' +

         SUBSTRING(RIGHT('00000' + CAST(run_time AS VARCHAR), 6), 5, 2)

         AS DATETIME) AS run_date_time,

        

         SUBSTRING(RIGHT('00000' + CAST(run_duration AS VARCHAR), 6), 1, 2) + ':' +

         SUBSTRING(RIGHT('00000' + CAST(run_duration AS VARCHAR), 6), 3, 2) + ':' +

         SUBSTRING(RIGHT('00000' + CAST(run_duration AS VARCHAR), 6), 5, 2)

         AS run_duration

      FROM

         sysjobhistory AS jh

            INNER JOIN sysjobs AS sj

               ON jh.[job_id] = sj.[job_id]

               AND jh.step_name = '(Job outcome)'

      WHERE

         -- -- we only want to know about jobs that were running because they were scheduled or because

         -- -- they were called by another job

            jh.MESSAGE LIKE 'The job succeeded.  The Job was invoked by Schedule%'

            OR jh.MESSAGE LIKE 'The job succeeded.  The job was invoked by User ' + @job_user + '%'

            OR jh.MESSAGE LIKE 'The job failed.  The Job was invoked by Schedule%'

            OR jh.MESSAGE LIKE 'The job failed.  The job was invoked by User ' + @job_user + '%'

   ) AS src

) AS src2

 

-- -- we have average start time and average duration.  now, let's get join in average runs per day by job name

INNER JOIN

(

   SELECT

      name AS job_name,

      AVG(runs_per_day) AS avg_runs_per_day

   FROM

   (

      SELECT

         NAME,

         COUNT(run_duration) AS runs_per_day

      FROM

      (

         SELECT

            sj.NAME,

           

            cast(  

            SUBSTRING(CAST(run_date AS VARCHAR), 1, 4) + '-' +

            SUBSTRING(CAST(run_date AS VARCHAR), 5, 2) + '-' +

            SUBSTRING(CAST(run_date AS VARCHAR), 7, 2) + ' ' +

            SUBSTRING(RIGHT('00000' + CAST(run_time AS VARCHAR), 6), 1, 2) + ':' +

            SUBSTRING(RIGHT('00000' + CAST(run_time AS VARCHAR), 6), 3, 2) + ':' +

            SUBSTRING(RIGHT('00000' + CAST(run_time AS VARCHAR), 6), 5, 2)

            AS DATETIME) AS run_date_time,

           

            SUBSTRING(RIGHT('00000' + CAST(run_duration AS VARCHAR), 6), 1, 2) + ':' +

            SUBSTRING(RIGHT('00000' + CAST(run_duration AS VARCHAR), 6), 3, 2) + ':' +

            SUBSTRING(RIGHT('00000' + CAST(run_duration AS VARCHAR), 6), 5, 2)

            AS run_duration

         FROM

            sysjobhistory AS jh

               INNER JOIN sysjobs AS sj

                  ON jh.[job_id] = sj.[job_id]

                  AND jh.step_name = '(Job outcome)'

         WHERE

         -- -- we only want to know about jobs that were running because they were scheduled or because

         -- -- they were called by another job

            jh.MESSAGE LIKE 'The job succeeded.  The Job was invoked by Schedule%'

            OR jh.MESSAGE LIKE 'The job succeeded.  The job was invoked by User ' + @job_user + '%'

            OR jh.MESSAGE LIKE 'The job failed.  The Job was invoked by Schedule%'

            OR jh.MESSAGE LIKE 'The job failed.  The job was invoked by User ' + @job_user + '%'

      ) AS src3

      GROUP BY

         name,

         CONVERT(VARCHAR, run_date_time,112)

      ) AS src4

      GROUP BY

         NAME

   ) AS src5

   ON src2.NAME = src5.job_name

 

INNER JOIN sysjobs AS src6

   ON src2.NAME = src6.NAME

  

GROUP BY

   src2.NAME,

   src5.avg_runs_per_day,

   src6.enabled

  

ORDER BY

   typical_start_time DESC,

   last_run_date_time ASC