Automated Benchmarking and Trend
Metrics Collection Techniques with T-SQL Part 2
Performance Counters via sysperfinfo
By Bill
Wunder
A couple of weeks ago I set out to pirate a phrase from the
life sciences to describe the shortcutting often used to
determine when and if a SQL Server is in need of a hardware
upgrade or possibly even an application design re-evaluation. In
a manner far too similar to that which Social Darwinism in the
early 20th century helped lay waste to any prospects of a humane
humanity and helped to propagate a world order of oppression and
violent wars, so is SQL Darwinism propping up an explosion of
big iron solutions to rather more modest data applications. Not
that I would want to even pretend that any flaws in current
hardware sizing practices carry anything even close to the
consequences for life on our planet as the might-is-right
syllogism spawned in part from Social Darwinism. Rather, my
contention is simply that in both cases there is a large and
inexplicable subjective element necessary to get from the
underlying science - or probably more appropriately the
underlying engineering in the case of SQL Darwinism - to the
overt manifestations.
As often as not, the decision on when to upgrade hardware is
based on the ubiquitous use-it-or-lose-it budgetary practice
over any quantifiable prediction of the need for more
throughput. As often as not the the determination of a target
upgrade platform is couched in some mystical boogie factor -
ever used "estimate the need and multiply by 2" calculus? - and
driven more by vendor marketing spiel than even the most
fundamental of formulae. In part, these shortfalls can be
overcome if the organization is well prepared to be able to
quantify the next move.
I don't want to spend to much time here expounding upon the
possibility that the business thought process that is SQL
Darwinism is flawed similarly to the socio-political thought
process that is Social Darwinism. I just wanted to stir that
idea up a little bit before I dive into the topic at hand. If
you'd like to explore that notion please refer to the discussion
and links I've provided in that earlier
SQL Darwinism article.
Here my intention is only to use that as spring board into what
seems to me to be a more reasonable approach: quantifiable
analytical trend analysis.
The way to objectively evaluate current behavioral qualities
and the performance and throughput trends of a SQL Server is of
course to measure them over time. In order to provide
fundamental meaning to any
time series analysis it is necessary to establish a base or
beginning point of measurement. System Monitor (aka Perfmon) is
commonly referenced as a valid tool to capture behavior over
time on a Microsoft Windows server. While system monitor does a
very good job of collecting and graphically depicting behavior
over time, it does not lend itself as nicely to ad hoc, a priori
or otherwise deductive analysis. Typically, when you do find
someone that has gone through an exercise in using Perfmon
captured data points in a trend analysis, there will be a time
intensive component in the analysis where the analyst has
manually moved data points from Perfmon into a spreadsheet or
database. One outstanding feature within SQL Server 2000 is that
the SQL Server Perfmon metrics are already available. Most in
table form in the system table master.dbo.sysperfinfo. A few
others from the system function ::fn_virtualfilestats. And even
some low level statistics accessible via the System Statistical
Functions and sp_monitor as we have already discovered in the
previous article:
Benchmarking Techniques Using T-SQL Part 1 - System Statistical
Functions. To continue to build upon that article, in this
installment we will first give sysperfinfo a good look, then we
can give due consideration to the file level metrics available
through ::fn_virtualfilestats.
sysperfinfo
sysperfino is a system supplied virtual table much like
sysprocesses and syslocks and a few others. Virtual tables are
like views with the exception being that the system virtual
tables are views into the SQL Server's protected memory space
rather than views based on tables stored in a database on disk.
It's somewhat amusing to contemplate - and undeniably true -
that when the SQL Server process is not running there is no data
in sysprocesses or sysperfinfo. Conversely, those 23 rows in
pubs.dbo.authors are there whether the SQL Server is running or
not. It's perplexing to note that sysperfinfo is not listed on
the Books Online "Monitoring Tools" page. Personally I think
it's an awesome monitoring tool component and based on some of
the comments I've heard over time my suspicion is there are
folks on the SQL Server team at Microsoft that might agree.
sysperfinfo contains rows to correspond to counters from
these System Monitor objects:
object name |
counter name |
instance name |
SQLServer:Access Methods |
Extent Deallocations/sec |
|
SQLServer:Access Methods |
Extents Allocated/sec |
|
SQLServer:Access Methods |
Forwarded Records/sec |
|
SQLServer:Access Methods |
FreeSpace Page Fetches/sec |
|
SQLServer:Access Methods |
FreeSpace Scans/sec |
|
SQLServer:Access Methods |
Full Scans/sec |
|
SQLServer:Access Methods |
Index Searches/sec |
|
SQLServer:Access Methods |
Mixed page allocations/sec |
|
SQLServer:Access Methods |
Page Deallocations/sec |
|
SQLServer:Access Methods |
Page Splits/sec |
|
SQLServer:Access Methods |
Pages Allocated/sec |
|
SQLServer:Access Methods |
Probe Scans/sec |
|
SQLServer:Access Methods |
Range Scans/sec |
|
SQLServer:Access Methods |
Scan Point Revalidations/sec |
|
SQLServer:Access Methods |
Skipped Ghosted Records/sec |
|
SQLServer:Access Methods |
Table Lock Escalations/sec |
|
SQLServer:Access Methods |
Workfiles Created/sec |
|
SQLServer:Access Methods |
Worktables Created/sec |
|
SQLServer:Access Methods |
Worktables From Cache Base |
|
SQLServer:Access Methods |
Worktables From Cache Ratio |
|
SQLServer:Buffer Manager |
AWE lookup maps/sec |
|
SQLServer:Buffer Manager |
AWE stolen maps/sec |
|
SQLServer:Buffer Manager |
AWE unmap calls/sec |
|
SQLServer:Buffer Manager |
AWE unmap pages/sec |
|
SQLServer:Buffer Manager |
AWE write maps/sec |
|
SQLServer:Buffer Manager |
Buffer cache hit ratio base |
|
SQLServer:Buffer Manager |
Buffer cache hit ratio |
|
SQLServer:Buffer Manager |
Checkpoint pages/sec |
|
SQLServer:Buffer Manager |
Database pages |
|
SQLServer:Buffer Manager |
Free list stalls/sec |
|
SQLServer:Buffer Manager |
Free pages |
|
SQLServer:Buffer Manager |
Lazy writes/sec |
|
SQLServer:Buffer Manager |
Page life expectancy |
|
SQLServer:Buffer Manager |
Page lookups/sec |
|
SQLServer:Buffer Manager |
Page reads/sec |
|
SQLServer:Buffer Manager |
Page writes/sec |
|
SQLServer:Buffer Manager |
Procedure cache pages |
|
SQLServer:Buffer Manager |
Readahead pages/sec |
|
SQLServer:Buffer Manager |
Reserved pages |
|
SQLServer:Buffer Manager |
Stolen pages |
|
SQLServer:Buffer Manager |
Target pages |
|
SQLServer:Buffer Manager |
Total pages |
|
SQLServer:Buffer Partition |
Free list empty/sec |
|
SQLServer:Buffer Partition |
Free list requests/sec |
|
SQLServer:Buffer Partition |
Free pages |
|
SQLServer:Cache Manager |
Cache Hit Ratio Base |
_Total |
SQLServer:Cache Manager |
Cache Hit Ratio Base |
Adhoc Sql Plans |
SQLServer:Cache Manager |
Cache Hit Ratio Base |
Cursors |
SQLServer:Cache Manager |
Cache Hit Ratio Base |
Execution Contexts |
SQLServer:Cache Manager |
Cache Hit Ratio Base |
Misc. Normalized Trees |
SQLServer:Cache Manager |
Cache Hit Ratio Base |
Prepared Sql Plans |
SQLServer:Cache Manager |
Cache Hit Ratio Base |
Procedure Plans |
SQLServer:Cache Manager |
Cache Hit Ratio Base |
Replication Procedure Plans |
SQLServer:Cache Manager |
Cache Hit Ratio Base |
Trigger Plans |
SQLServer:Cache Manager |
Cache Hit Ratio |
_Total |
SQLServer:Cache Manager |
Cache Hit Ratio |
Adhoc Sql Plans |
SQLServer:Cache Manager |
Cache Hit Ratio |
Cursors |
SQLServer:Cache Manager |
Cache Hit Ratio |
Execution Contexts |
SQLServer:Cache Manager |
Cache Hit Ratio |
Misc. Normalized Trees |
SQLServer:Cache Manager |
Cache Hit Ratio |
Prepared Sql Plans |
SQLServer:Cache Manager |
Cache Hit Ratio |
Procedure Plans |
SQLServer:Cache Manager |
Cache Hit Ratio |
Replication Procedure Plans |
SQLServer:Cache Manager |
Cache Hit Ratio |
Trigger Plans |
SQLServer:Cache Manager |
Cache Object Counts |
_Total |
SQLServer:Cache Manager |
Cache Object Counts |
Adhoc Sql Plans |
SQLServer:Cache Manager |
Cache Object Counts |
Cursors |
SQLServer:Cache Manager |
Cache Object Counts |
Execution Contexts |
SQLServer:Cache Manager |
Cache Object Counts |
Misc. Normalized Trees |
SQLServer:Cache Manager |
Cache Object Counts |
Prepared Sql Plans |
SQLServer:Cache Manager |
Cache Object Counts |
Procedure Plans |
SQLServer:Cache Manager |
Cache Object Counts |
Replication Procedure Plans |
SQLServer:Cache Manager |
Cache Object Counts |
Trigger Plans |
SQLServer:Cache Manager |
Cache Pages |
_Total |
SQLServer:Cache Manager |
Cache Pages |
Adhoc Sql Plans |
SQLServer:Cache Manager |
Cache Pages |
Cursors |
SQLServer:Cache Manager |
Cache Pages |
Execution Contexts |
SQLServer:Cache Manager |
Cache Pages |
Misc. Normalized Trees |
SQLServer:Cache Manager |
Cache Pages |
Prepared Sql Plans |
SQLServer:Cache Manager |
Cache Pages |
Procedure Plans |
SQLServer:Cache Manager |
Cache Pages |
Replication Procedure Plans |
SQLServer:Cache Manager |
Cache Pages |
Trigger Plans |
SQLServer:Cache Manager |
Cache Use Counts/sec |
_Total |
SQLServer:Cache Manager |
Cache Use Counts/sec |
Adhoc Sql Plans |
SQLServer:Cache Manager |
Cache Use Counts/sec |
Cursors |
SQLServer:Cache Manager |
Cache Use Counts/sec |
Execution Contexts |
SQLServer:Cache Manager |
Cache Use Counts/sec |
Misc. Normalized Trees |
SQLServer:Cache Manager |
Cache Use Counts/sec |
Prepared Sql Plans |
SQLServer:Cache Manager |
Cache Use Counts/sec |
Procedure Plans |
SQLServer:Cache Manager |
Cache Use Counts/sec |
Replication Procedure Plans |
SQLServer:Cache Manager |
Cache Use Counts/sec |
Trigger Plans |
SQLServer:Databases |
Active Transactions |
(one row per database) |
SQLServer:Databases |
Backup/Restore Throughput/sec |
(one row per database) |
SQLServer:Databases |
Bulk Copy Rows/sec |
(one row per database) |
SQLServer:Databases |
Bulk Copy Throughput/sec |
(one row per database) |
SQLServer:Databases |
Data File(s) Size (KB) |
(one row per database) |
SQLServer:Databases |
DBCC Logical Scan Bytes/sec |
(one row per database) |
SQLServer:Databases |
Log Bytes Flushed/sec |
(one row per database) |
SQLServer:Databases |
Log Cache Hit Ratio Base |
(one row per database) |
SQLServer:Databases |
Log Cache Hit Ratio |
(one row per database) |
SQLServer:Databases |
Log Cache Reads/sec |
(one row per database) |
SQLServer:Databases |
Log File(s) Size (KB) |
(one row per database) |
SQLServer:Databases |
Log File(s) Used Size (KB) |
(one row per database) |
SQLServer:Databases |
Log Flush Wait Time |
(one row per database) |
SQLServer:Databases |
Log Flush Waits/sec |
(one row per database) |
SQLServer:Databases |
Log Flushes/sec |
(one row per database) |
SQLServer:Databases |
Log Growths |
(one row per database) |
SQLServer:Databases |
Log Shrinks |
(one row per database) |
SQLServer:Databases |
Log Truncations |
(one row per database) |
SQLServer:Databases |
Percent Log Used |
(one row per database) |
SQLServer:Databases |
Repl. Pending Xacts |
(one row per database) |
SQLServer:Databases |
Repl. Trans. Rate |
(one row per database) |
SQLServer:Databases |
Shrink Data Movement Bytes/sec |
(one row per database) |
SQLServer:Databases |
Transactions/sec |
(one row per database) |
SQLServer:General Statistics |
Logins/sec |
|
SQLServer:General Statistics |
Logouts/sec |
|
SQLServer:General Statistics |
User Connections |
|
SQLServer:Latches |
Average Latch Wait Time (ms) |
|
SQLServer:Latches |
Average Latch Wait Time Base |
|
SQLServer:Latches |
Latch Waits/sec |
|
SQLServer:Latches |
Total Latch Wait Time (ms) |
|
SQLServer:Locks |
Average Wait Time (ms) |
_Total |
SQLServer:Locks |
Average Wait Time (ms) |
Database |
SQLServer:Locks |
Average Wait Time (ms) |
Extent |
SQLServer:Locks |
Average Wait Time (ms) |
Key |
SQLServer:Locks |
Average Wait Time (ms) |
Page |
SQLServer:Locks |
Average Wait Time (ms) |
RID |
SQLServer:Locks |
Average Wait Time (ms) |
Table |
SQLServer:Locks |
Average Wait Time Base |
_Total |
SQLServer:Locks |
Average Wait Time Base |
Database |
SQLServer:Locks |
Average Wait Time Base |
Extent |
SQLServer:Locks |
Average Wait Time Base |
Key |
SQLServer:Locks |
Average Wait Time Base |
Page |
SQLServer:Locks |
Average Wait Time Base |
RID |
SQLServer:Locks |
Average Wait Time Base |
Table |
SQLServer:Locks |
Lock Requests/sec |
_Total |
SQLServer:Locks |
Lock Requests/sec |
Database |
SQLServer:Locks |
Lock Requests/sec |
Extent |
SQLServer:Locks |
Lock Requests/sec |
Key |
SQLServer:Locks |
Lock Requests/sec |
Page |
SQLServer:Locks |
Lock Requests/sec |
RID |
SQLServer:Locks |
Lock Requests/sec |
Table |
SQLServer:Locks |
Lock Timeouts/sec |
_Total |
SQLServer:Locks |
Lock Timeouts/sec |
Database |
SQLServer:Locks |
Lock Timeouts/sec |
Extent |
SQLServer:Locks |
Lock Timeouts/sec |
Key |
SQLServer:Locks |
Lock Timeouts/sec |
Page |
SQLServer:Locks |
Lock Timeouts/sec |
RID |
SQLServer:Locks |
Lock Timeouts/sec |
Table |
SQLServer:Locks |
Lock Wait Time (ms) |
_Total |
SQLServer:Locks |
Lock Wait Time (ms) |
Database |
SQLServer:Locks |
Lock Wait Time (ms) |
Extent |
SQLServer:Locks |
Lock Wait Time (ms) |
Key |
SQLServer:Locks |
Lock Wait Time (ms) |
Page |
SQLServer:Locks |
Lock Wait Time (ms) |
RID |
SQLServer:Locks |
Lock Wait Time (ms) |
Table |
SQLServer:Locks |
Lock Waits/sec |
_Total |
SQLServer:Locks |
Lock Waits/sec |
Database |
SQLServer:Locks |
Lock Waits/sec |
Extent |
SQLServer:Locks |
Lock Waits/sec |
Key |
SQLServer:Locks |
Lock Waits/sec |
Page |
SQLServer:Locks |
Lock Waits/sec |
RID |
SQLServer:Locks |
Lock Waits/sec |
Table |
SQLServer:Locks |
Number of Deadlocks/sec |
_Total |
SQLServer:Locks |
Number of Deadlocks/sec |
Database |
SQLServer:Locks |
Number of Deadlocks/sec |
Extent |
SQLServer:Locks |
Number of Deadlocks/sec |
Key |
SQLServer:Locks |
Number of Deadlocks/sec |
Page |
SQLServer:Locks |
Number of Deadlocks/sec |
RID |
SQLServer:Locks |
Number of Deadlocks/sec |
Table |
SQLServer:Memory Manager |
Connection Memory (KB) |
|
SQLServer:Memory Manager |
Granted Workspace Memory (KB) |
|
SQLServer:Memory Manager |
Lock Blocks Allocated |
|
SQLServer:Memory Manager |
Lock Blocks |
|
SQLServer:Memory Manager |
Lock Memory (KB) |
|
SQLServer:Memory Manager |
Lock Owner Blocks Allocated |
|
SQLServer:Memory Manager |
Lock Owner Blocks |
|
SQLServer:Memory Manager |
Maximum Workspace Memory (KB) |
|
SQLServer:Memory Manager |
Memory Grants Outstanding |
|
SQLServer:Memory Manager |
Memory Grants Pending |
|
SQLServer:Memory Manager |
Optimizer Memory (KB) |
|
SQLServer:Memory Manager |
SQL Cache Memory (KB) |
|
SQLServer:Memory Manager |
Target Server Memory(KB) |
|
SQLServer:Memory Manager |
Total Server Memory (KB) |
|
SQLServer:SQL Statistics |
Auto-Param Attempts/sec |
|
SQLServer:SQL Statistics |
Batch Requests/sec |
|
SQLServer:SQL Statistics |
Failed Auto-Params/sec |
|
SQLServer:SQL Statistics |
Safe Auto-Params/sec |
|
SQLServer:SQL Statistics |
SQL Compilations/sec |
|
SQLServer:SQL Statistics |
SQL Re-Compilations/sec |
|
SQLServer:SQL Statistics |
Unsafe Auto-Params/sec |
|
SQLServer:User Settable |
Query |
User counter 1 |
SQLServer:User Settable |
Query |
User counter 10 |
SQLServer:User Settable |
Query |
User counter 2 |
SQLServer:User Settable |
Query |
User counter 3 |
SQLServer:User Settable |
Query |
User counter 4 |
SQLServer:User Settable |
Query |
User counter 5 |
SQLServer:User Settable |
Query |
User counter 6 |
SQLServer:User Settable |
Query |
User counter 7 |
SQLServer:User Settable |
Query |
User counter 8 |
SQLServer:User Settable |
Query |
User counter 9 |
The data in sysperfinfo represents a real time value. With
only a little effort you can easily capture periodic snapshots
of the data you deem interesting. There are a few different data
capture methods necessary in order to assure proper
interpretation of the data collected. Some metrics such as the
"Data File(s) Size (KB)" or "Log File(s) Used Size (KB)"
counters of the database object provide a point in time value.
The value represent the actual size of the data files or the
actual amount of the log now in use. If you capture these values
it's a pretty straight forward query to determine the file
growth patterns.
If you refer to the sysperfinfo
scripts you can review the table schema and
stored procedures I use to capture sysperfinfo data and a few
T-SQL examples of how I use that data. It's as easy to render
the results through your favorite charting tool as it is T-SQL
and often provides a much more intuitive view for those non-DBA
types we all have to deal with from time to time.
For example, a query of the collected data can show you when
data files grew and allow you to plot a growth trend:
select DbName, cast(max(SampleDate) as varchar(11)) as [Latest Date at this size],
DataFileSize_KB,
LogFileSize_KB
from admin.dbo.DbPerfHistory
where DbName like @DbName + '%'
group by DbName, DataFileSize_KB, LogFileSize_KB
order by case DBName when '_Total' then 1 else 0 end, DbName, max(SampleDate) desc
Other metrics are accumulated values and increase over time
such as the "Transactions/sec" counter of the Database
performance object or the "Readahead pages/sec" of the Buffer
Manager object. These values require that you know the elapsed
time between samples in order to produce a meaningful metric
from the collected data. In general the algorithm for this type
of metric is:
(current counter value - previous counter value) /
DateDiff(second, previous sample datetime, this sample datetime)
;
where second can be replaced with another time division
when appropriate. When you are using a metric that is
described as value/sec you're probably best to stay with
seconds as your time division.
You could pre-calculate such values at insert time such as in
this example taken from the admin.dbo.GetPerfHistory procedure
included in the
sysperfinfo scripts:
update d
set LastTranRateSample = t.TxnPerSec,
LastSampleDt = @CurDt,
CurTxnPerSec = case when ((t.TxnPerSec - d.LastTranRateSample) >= 0)
then (t.TxnPerSec - d.LastTranRateSample)
/ DateDiff(s, d.LastSampleDt, @CurDt)
else (t.TxnPerSec) / DateDiff(s, d.LastSampleDt, @CurDt)
end, -- handle a counter reset
MinTxnPerSec = case when ((t.TxnPerSec - d.LastTranRateSample)
/ DateDiff(s,d.LastSampleDt,@CurDt) < d.MinTxnPerSec)
then case when ((t.TxnPerSec - d.LastTranRateSample) >= 0)
then (t.TxnPerSec - d.LastTranRateSample)
/ DateDiff(s,d.LastSampleDt,@CurDt)
else (t.TxnPerSec) / DateDiff(s,d.LastSampleDt,@CurDt)
end
else d.MinTxnPerSec
end,
MinTxnDt = case when ((t.TxnPerSec - d.LastTranRateSample)
/ DateDiff(s,d.LastSampleDt,@CurDt) < d.MinTxnPerSec)
then @CurDt
else d.MinTxnDt
end,
MaxTxnPerSec = case when ((t.TxnPerSec - d.LastTranRateSample)
/ DateDiff(s,d.LastSampleDt,@CurDt) > d.MaxTxnPerSec)
then case when ((t.TxnPerSec - d.LastTranRateSample) >= 0)
then (t.TxnPerSec - d.LastTranRateSample)
/ DateDiff(s,d.LastSampleDt,@CurDt)
else (t.TxnPerSec) / DateDiff(s,d.LastSampleDt,@CurDt)
end
else d.MaxTxnPerSec
end,
MaxTxnDt = case when ((t.TxnPerSec - d.LastTranRateSample)
/DateDiff(s,d.LastSampleDt,@CurDt) > d.MaxTxnPerSec)
then @CurDt
else d.MaxTxnDt
end,
TransActivityDetected = case when (t.TxnPerSec - d.LastTranRateSample) = 0
then TransActivityDetected
else TransActivityDetected + 1
end
from @t t
inner join admin.dbo.DbPerfHistory d
on t.DbName = d.DbName
where SampleDate = @today -- work on today's record
and ( LastSampleDt < @CurDt) -- unless inserted this time
And then to query the collected data thusly to show you the
relative throughput patterns over time:
select p1.dbName,
Datepart(yyyy, p1.SampleDate) as year,
Datepart(mm, p1.SampleDate) as month,
cast(avg(p1.MinTxnPerSec) as numeric(12,2)) as [AvgDailyMinTxn/Sec],
cast(avg(p1.MaxTxnPerSec) as numeric(12,2)) as [AvgDailyMaxTxn/Sec],
avg(cast(cast(p1.TransActivityDetected as numeric(12,2))
/cast(p2.TransActivityDetected as numeric(12,2)) * 100 as int))
as [Avg%Active/Day]
from admin.dbo.dbperfhistory p1
inner join admin.dbo.dbperfhistory p2
on p1.SampleDate = p2.SampleDate
where Datepart(d, p1.SampleDate) = 1
and p2.DbName = '_Total'
and p1.dbName like @DbName + '%' -- allows query to be for all or for a specified db
group by p1.dbName,Datepart(yyyy, p1.SampleDate),Datepart(mm, p1.SampleDate)
order by case p1.DBName when '_Total' then 1 else 0 end,
p1.dbname,
Datepart(yyyy, p1.SampleDate) desc,Datepart(mm, p1.SampleDate) desc
Or , if it makes more sense to the way you want to use the
data, you could do the calculation within the trend analysis
query such as this query to compute the batch request/second
trend from a once a day sample table:
select p1.SampleDate
, (p1.BatchRequestPerSec - p2.BatchRequestPerSec)
/(24*60*60) [BatchRequest/Sec]-- seconds in a day
from admin.dbo.SrvPerfHistory p1
inner join admin.dbo.SrvPerfHistory p2
on p1.SampleDate = p2.SampleDate + 1
order by p1.SampleDate Desc
Lastly there is the data that includes a column for the the
current value and a base value such as the "Average Wait Time(ms)"
counter of the Locks object or "Cache Hit Ratio" of the Cache
Manager object. For example to insert a meaningful Buffer Cache
Hit Ratio into a trend capture table you would need to compute
the ratio of the 'Buffer Cache Hit Ratio' rows data value and
the 'Buffer Cache Hit Ratio Base' rows data value in sysperfinfo:
insert admin.dbo.srvperfhistory (BufferCacheHitRatio)
select cast(p1.cntr_value as numeric(12,2))
/ cast(p2.cntr_value as numeric(12,2))
from master.dbo.sysperfinfo p1
inner join master.dbo.sysperfinfo p2
on p1.object_name = p2.object_name
where p1.object_name = 'SQLServer:Buffer Manager'
and p1.counter_name = 'Buffer cache hit ratio'
and p2.counter_name = 'Buffer cache hit ratio base'
and p1.instance_name = p2.instance_name
Alternately you could simply record the counter value and the
base value in the data capture table and compute the ratio at
query time.
With that assortment of techniques you can easily define a
regularly scheduled task that at it's first execution will
provide you with a baseline for your SQL Server and at each
subsequent execution will refine the behavioral trend
documentation for your SQL Server and customize the reporting
queries to meet the needs of your organization.
I'm not trying to spew dogma here as a SQL Darwinist might,
I'm just trying to give you adequate engineering tools to meet
the needs of your organization. The technique I use is to
execute the the admin.dbo.GetPerfHistory procedure you can
download from the
scripts provided in the
swwug.org Script Library to complement the sysperfinfo
discussion in this article every half hour from a SQL Agent job.
This gives me a new record each night at midnight and then
throughout the day I can update such things as the min and max
transactions per second so I can make a determination as to when
during the day a server is working the hardest (and when it's
not working so hard which is useful information when you are
trying to establish a maintenance window for those ugly delete
archive processes or to reorg your indexes.
::fn_virtualfilestats
For all practical purposes you can get performance and
throughput details at the database file level through the System
Statistical Function fn_virtualfilestats using the very same
techniques I have outlined above for the system virtual table
sysperfinfo. It's actually a rather limited set of metrics and
because you need to provide the function with a database id and
a file id some special tactics are necessary when collection
data.
I certainly don't want to give the idea that
fn_virtulafilestats - or sysperfinfo for that matter - are only
useful for occasional data collection. These data sources can
provide powerful troubleshooting information when your are in
the thick of a problem. I strongly encourage you to become
familiar with fn_virtualfilestats and add it to your tool belt.
It has some great information.
For baseline and trend analysis I suggest a stored procedure
that uses sp_msforeachdb to iterate through the databases and
then captures a row for each file in each database once a day.
You may want to start with the
stored procedure I have posted. You can schedule it once a day in
the SQL Agent or you could consider adding it as a job step in
the job suggested above to capture sysperfinfo data. There are
only 5 data points for each file (Reads, Writes, Read Bytes,
Write Bytes and Stalls) so you end up with a fairly narrow but
meaty row.
With these T-SQL based resources for establishing baselines
and identifying performance and throughput trends, there really
is little reason for anyone to have to resort to 20th century
approaches like SQL Darwinism to assure proper scale and
hardware class for a SQL Server database in 2004. Deciding what
to include in your benchmarking and trend capture collection is
again another science. Each application will likely require a
different set of data to properly do the job. For more details
on selecting counters and how they can be used see the
"Monitoring with System Monitor" chapter of SQL Server Books
Online, Ken Henderson's awesome The Guru's Guide to SQL Server
Architecture and Internals, Brian Kelly's very practical e-book
Start to finish Guide to SQL Server Performance Tuning and the
performance monitor tips at
sql-server-performance.com (look toward the bottom of the
home page for the tips index), and don't overlook the incredible
group of unbelievable smart, savvy and generous folks on the SQL
Server Worldwide User Groups list server at
SQL Server 2000 discussions as you define the correct
baseline and trend data points for your organization.
There are two scripts referenced above that you may want to
investigate for additional details for this article:
admin db - baseline and trends using sysperfinfo
use admin
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_NULLS ON
GO
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[GetPerfHistory]') and OBJECTPROPERTY(id, N'IsProcedure') = 1)
drop procedure [dbo].[GetPerfHistory]
GO
Create proc dbo.GetPerfHistory
as
/*******************************************************************************************************
* admin.dbo.GetPerfHistory
* Creator: Bill W
* Outline: query the system table sysperfinfo daily to capture:
* DB file size
* Log File size
* Log File size used
* Table Scans
* Data Cache Hit Ratio
* Procedure Cache Hit Ratio
* query sysperfinfo throughout the day to capture a daily distribution:
* transaction/sec
*
* How it Works: The procedure is run from the SQL Agent scheduler on a regular interval.
*
* usage:
EXECUTE admin.dbo.GetPerfHistory
* Notes: Query this table to determine the avg, max and min transactions per second by Date
* use daily deltas to determine change trends
* Modifications
*
********************************************************************************************************/
declare @today datetime,
@CurDt datetime
declare @t table (DbName varchar(30) primary key,
TxnPerSec numeric(16,6))
set nocount on
-- get a timestamp for now and midnight
set @CurDt = getDate()
set @today = cast(@CurDt as varchar(11))
-- don't do anything if wrong database
if db_name() <> 'admin'
begin
raiserror ('This utility may only be installed in the admin database!',16,1)
return -1
end
-- init the tables if first time on this server
if (object_id('dbo.DbPerfHistory','U') is null)
create table dbo.DbPerfHistory (DbName varchar(30) NOT NULL,
SampleDate datetime NOT NULL,
DataFileSize_KB int NULL,
LogFileSize_KB int NULL,
LogFileSizeUsed_KB int NULL,
FirstTranRateSample int NULL,
LastTranRateSample int NULL,
CurTxnPerSec numeric(16,6) NULL,
MinTxnPerSec numeric(16,6) NULL,
MinTxnDt datetime NULL,
MaxTxnPerSec numeric(16,6) NULL,
MaxTxnDt datetime NULL,
TransActivityDetected int NULL,
FirstSampleDt datetime NULL,
LastSampleDt datetime NULL,
constraint pk_DbPerfHistory__SampleDate__DbName primary key
(SampleDate, DbName))
if (object_id('dbo.SrvPerfHistory','U') is null)
create table dbo.SrvPerfHistory (SampleDate datetime NOT NULL,
TableScans numeric(16,6) NULL,
BatchRequestPerSec numeric(16,6) NULL,
DataCacheHitRatio numeric(12,2) NULL,
DataCacheHitRatioBase numeric(12,2) NULL,
ProcCacheHitRatio numeric(12,2) NULL,
ProcCacheHitRatioBase numeric(12,2) NULL,
constraint pk_SrvPerfHistory__SampleDate primary key
(SampleDate))
-- can add other values that require intraday range tracking here
insert @t (DbName, TxnPerSec)
select substring(instance_name,1,30),
cast(cntr_value as numeric(16,6))
from master.dbo.sysperfinfo
where counter_name= 'Transactions/sec'
-- if no rows for today, init a row
insert admin.dbo.DbPerfHistory (DbName,
FirstTranRateSample,
FirstSampleDt,
LastTranRateSample,
LastSampleDt,
CurTxnPerSec,
MinTxnPerSec,
MinTxnDt,
MaxTxnPerSec,
MaxTxnDt,
TransActivityDetected,
SampleDate)
select DbName,
t.txnPerSec,
@CurDt,
t.txnPerSec,
@CurDt,
999999999.999999,
999999999.999999,
@CurDt,
0,
@CurDt,
0,
@today
from @t t
where not exists(select 1 from admin.dbo.DbPerfHistory
where dbName = t.DbName
and SampleDate = @today)
if not exists (select 1 from admin.dbo.SrvPerfHistory
where SampleDate = @today)
begin
-- if buffer cache hit ratio getting smaller and scans increasing, new queries not using indexes
insert admin.dbo.SrvPerfHistory (SampleDate, TableScans)
select @today, p.cntr_value
from master.dbo.sysperfinfo p
where p.object_name = 'SQLServer:Access Methods'
and p.counter_name = 'Full Scans/sec'
-- as falls away from 100% more memory can help
--select cast(cast(p2.cntr_value as numeric(14,4))/cast(p1.cntr_value as numeric(14,4)) as numeric(6,4)) * 100
-- calc daily delta to plot over time (elim days where counter rolls over or restarts)
update h
set DataCacheHitRatio = p1.cntr_value,
DataCacheHitRatioBase = p2.cntr_value
from master.dbo.sysperfinfo p1
inner join master.dbo.sysperfinfo p2
on p1.object_name = p2.object_name
and p1.instance_name = p2.instance_name
cross join admin.dbo.SrvPerfHistory h
where p1.object_name = 'SQLServer:Buffer Manager'
and p1.counter_name = 'Buffer cache hit ratio'
and p2.counter_name = 'Buffer cache hit ratio base'
and h.SampleDate = @today -- work on today's record
-- lower value may mean high dynamic sql use
--select cast(cast(p1.cntr_value as numeric(14,4))/cast(p2.cntr_value as numeric(14,4)) as numeric(6,4)) * 100
-- calc daily delta to plot over time (elim days where counter rolls over or restarts)
update h
set ProcCacheHitRatio = p1.cntr_value,
ProcCacheHitRatioBase = p2.cntr_value
from master.dbo.sysperfinfo p1
inner join master.dbo.sysperfinfo p2
on p1.object_name = p2.object_name
and p1.instance_name = p2.instance_name
cross join admin.dbo.SrvPerfHistory h
where p1.object_name = 'SQLServer:Cache Manager'
and p1.instance_name = 'Prepared Sql Plans'
and p1.counter_name = 'Cache Hit Ratio'
and p2.counter_name = 'Cache Hit Ratio Base'
and h.SampleDate = @today -- work on today's record
-- use batch request per second as a general indicater of read/write activity over time
-- calc daily delta to plot over time (elim days where counter rolls over or restarts)
update h
set BatchRequestPerSec = p.cntr_value
from master.dbo.sysperfinfo p
cross join admin.dbo.SrvPerfHistory h
where p.object_name = 'SQLServer:SQL Statistics'
and p.counter_name = 'Batch Requests/sec'
and h.SampleDate > = @today -- work on today's record
end
if exists (select 1 from admin.dbo.DbPerfHistory
where SampleDate = @today
and FirstSampleDt = @CurDt)
begin
update h
set DataFileSize_KB = p.cntr_value
from master.dbo.sysperfinfo p
inner join admin.dbo.DbPerfHistory h
on p.instance_name = h.DbName
where p.counter_name = 'Data File(s) Size (KB)'
and h.SampleDate = @today -- work on today's record
and h.FirstSampleDt = @CurDt -- only if inserted this time
update h
set LogFileSize_KB = p.cntr_value
from master.dbo.sysperfinfo p
inner join admin.dbo.DbPerfHistory h
on p.instance_name = h.DbName
where p.counter_name = 'Log File(s) Size (KB)'
and h.SampleDate = @today -- work on today's record
and h.FirstSampleDt = @CurDt -- only if inserted this time
update h
set LogFileSizeUsed_KB = p.cntr_value
from master.dbo.sysperfinfo p
inner join admin.dbo.DbPerfHistory h
on p.instance_name = h.DbName
where p.counter_name = 'Log File(s) Used Size (KB)'
and h.SampleDate = @today -- work on today's record
and h.FirstSampleDt = @CurDt -- only if inserted this time
end
else
-- if already rows for today, do tran rate aggregations
-- use 0 as LastTranRateSample if counter has been reset
-- used values returned in perfmon to determine calculations
update d
set LastTranRateSample = t.TxnPerSec,
LastSampleDt = @CurDt,
CurTxnPerSec = case when ((t.TxnPerSec - d.LastTranRateSample) >= 0)
then (t.TxnPerSec - d.LastTranRateSample) / DateDiff(s, d.LastSampleDt, @CurDt)
else (t.TxnPerSec) / DateDiff(s, d.LastSampleDt, @CurDt)
end, -- handle a counter reset
MinTxnPerSec = case when ((t.TxnPerSec - d.LastTranRateSample) / DateDiff(s,d.LastSampleDt,@CurDt) < d.MinTxnPerSec)
then case when ((t.TxnPerSec - d.LastTranRateSample) >= 0)
then (t.TxnPerSec - d.LastTranRateSample) / DateDiff(s,d.LastSampleDt,@CurDt)
else (t.TxnPerSec) / DateDiff(s,d.LastSampleDt,@CurDt)
end
else d.MinTxnPerSec
end,
MinTxnDt = case when ((t.TxnPerSec - d.LastTranRateSample) / DateDiff(s,d.LastSampleDt,@CurDt) < d.MinTxnPerSec)
then @CurDt
else d.MinTxnDt
end,
MaxTxnPerSec = case when ((t.TxnPerSec - d.LastTranRateSample) / DateDiff(s,d.LastSampleDt,@CurDt) > d.MaxTxnPerSec)
then case when ((t.TxnPerSec - d.LastTranRateSample) >= 0)
then (t.TxnPerSec - d.LastTranRateSample) / DateDiff(s,d.LastSampleDt,@CurDt)
else (t.TxnPerSec) / DateDiff(s,d.LastSampleDt,@CurDt)
end
else d.MaxTxnPerSec
end,
MaxTxnDt = case when ((t.TxnPerSec - d.LastTranRateSample)/DateDiff(s,d.LastSampleDt,@CurDt) > d.MaxTxnPerSec)
then @CurDt
else d.MaxTxnDt
end,
TransActivityDetected = case when (t.TxnPerSec - d.LastTranRateSample) = 0
then TransActivityDetected
else TransActivityDetected + 1
end
from @t t
inner join admin.dbo.DbPerfHistory d
on t.DbName = d.DbName
where SampleDate = @today -- work on today's record
and ( LastSampleDt < @CurDt) -- unless inserted this time
return
GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO
use admin
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_NULLS ON
GO
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[DbActivityTrend]') and OBJECTPROPERTY(id, N'IsProcedure') = 1)
drop procedure [dbo].[DbActivityTrend]
GO
CREATE PROCEDURE [dbo].[DbActivityTrend]
@DbName varchar(30) = ''
AS
/*******************************************************************************************************
* admin.dbo.DbActivityTrend
* Creator: Bill Wunder
* Outline: Query the stored samples from master..sysperfinfo
* to display the transaction rate history for a database
*
*
* usage:
EXECUTE admin.dbo.DbActivityTrend customer
*
* Modifications
* developer name date brief description
* ------------------ -------- ------------------------------------------------------------
*
********************************************************************************************************/
-- declare variables
-- set session
SET NOCOUNT ON
-- create temp tables
-- body of stored procedure
select p1.dbName,
cast((cast(p1.LastTranRateSample as numeric(12,2)) - cast(p1.FirstTranRateSample as numeric(12,2)))/cast(datediff(s, p1.FirstSampleDt, p1.LastSampleDt) as numeric(12,2)) as numeric(12,2)) as avgTxnPerSec,
cast(p1.MinTxnPerSec as numeric(12,2)) as MinTxnPerSec,
cast(p1.MaxTxnPerSec as numeric(12,2)) as MaxTxnPerSec,
cast(cast(p1.TransActivityDetected as numeric(12,2))/cast(p2.TransActivityDetected as numeric(12,2)) * 100 as int) as PctActive,
convert(varchar(21),p1.SampleDate,102) as [Date],
datepart(dw, p1.SampleDate) as [Day of Week]
from admin.dbo.dbperfhistory p1
inner join admin.dbo.dbperfhistory p2
on p1.SampleDate = p2.SampleDate
where p1.DbName like @dbName + '%'
and p2.DbName = '_Total'
order by p1.dbname, p1.sampledate
RETURN
GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO
use admin
GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[DbLogSizeTrend]') and OBJECTPROPERTY(id, N'IsProcedure') = 1)
drop procedure [dbo].[DbLogSizeTrend]
GO
CREATE PROCEDURE [dbo].[DbLogSizeTrend]
@DbName sysname = null
AS
/*******************************************************************************************************
* admin.dbo.dbLogSizeTrend
* Creator: Bill Wunder
* Outline: show log space over time
* usage:
EXECUTE admin.dbo.dbLogSizeTrend
*
* Modifications
* developer name date brief description
* ------------------ -------- ------------------------------------------------------------
*
********************************************************************************************************/
-- declare variables
-- create temp tables
-- set session
SET NOCOUNT ON
-- body of stored procedure
if @DbName is null -- all
select DbName, cast(max(SampleDate) as varchar(11)) as [Latest Date at this size], LogFileSize_KB, LogFileSizeUsed_KB, datepart(dw, SampleDate) as [weekday]
from admin.dbo.DbPerfHistory
group by DbName, LogFileSize_KB, LogFileSizeUsed_KB, datepart(dw, SampleDate)
order by DbName, max(SampleDate)
else -- only one db
select Dbname, cast(max(SampleDate) as varchar(11)) as [Latest date at this size], LogFileSize_KB, LogFileSizeUsed_KB, datepart(dw, SampleDate) as [weekday]
from admin.dbo.DbPerfHistory
where DbName = @DbName
group by DbName, LogFileSize_KB , LogFileSizeUsed_KB, datepart(dw, SampleDate)
order by max(SampleDate)
RETURN
GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO
use admin
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_NULLS ON
GO
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[DbSizeTrend]') and OBJECTPROPERTY(id, N'IsProcedure') = 1)
drop procedure [dbo].[DbSizeTrend]
GO
CREATE PROCEDURE [dbo].[DbSizeTrend]
@DbName sysname = null
AS
/*******************************************************************************************************
* admin.dbo.dbSizeTrend
* Creator: Bill Wunder
* Outline: show data space over time
* does not include log space
*
*
* usage:
EXECUTE admin.dbo.dbSizeTrend
*
* Modifications
* developer name date brief description
* ------------------ -------- ------------------------------------------------------------
*
********************************************************************************************************/
-- declare variables
-- create temp tables
-- set session
SET NOCOUNT ON
-- body of stored procedure
if @DbName is null -- all
select DbName, max(SampleDate) as [Latest Date at this size], DataFileSize_KB
from admin.dbo.DbPerfHistory
group by DbName, DataFileSize_KB
order by DbName, max(SampleDate)
else -- only one db
select Dbname, max(SampleDate) as [Latest date at this size], DataFileSize_KB
from admin.dbo.DbPerfHistory
where DbName = @DbName
group by DbName, DataFileSize_KB
order by max(SampleDate)
RETURN
GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
use admin
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_NULLS ON
GO
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[GetSrvActivityTrend]') and OBJECTPROPERTY(id, N'IsProcedure') = 1)
drop procedure [dbo].[GetSrvActivityTrend]
GO
CREATE PROCEDURE [dbo].[GetSrvActivityTrend]
@DbName sysname = null
AS
/*******************************************************************************************************
* admin.dbo.GetSrvActivityTrend
* Creator: Bill Wunder
* Outline: show server activity over time
* usage:
EXECUTE admin.dbo.GetSrvActivityTrend
*
* Modifications
* developer name date brief description
* ------------------ -------- ------------------------------------------------------------
*
********************************************************************************************************/
-- declare variables
-- create temp tables
-- set session
SET NOCOUNT ON
-- body of stored procedure
select p1.SampleDate
, (p1.TableScans - p2.TableScans)/(24*60*60) [TableScans/Sec]-- seconds in a day
, (p1.BatchRequestPerSec - p2.BatchRequestPerSec)/(24*60*60) [BatchRequest/Sec]-- seconds in a day
, p1.DataCacheHitRatio/p1.DataCacheHitRatioBase [Data Cache Hit Ratio]
, p1.ProcCacheHitRatio/p1.ProcCacheHitRatioBase [Procedure Cache Hit Ratio]
from admin.dbo.SrvPerfHistory p1
inner join admin.dbo.SrvPerfHistory p2
on p1.SampleDate = p2.SampleDate + 1
order by p1.SampleDate Desc
RETURN
GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO
admin db - db file performance baseline and trends using ::fn_virtualfilestats
use admin
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_NULLS ON
GO
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[GetFileStats]') and OBJECTPROPERTY(id, N'IsProcedure') = 1)
drop procedure [dbo].[GetFileStats]
GO
create procedure dbo.GetFileStats
/*******************************************************************************************************
* dbo.GetFileStats.PRC
* Creator: Bill Wunder
*
* Description: create a trend history of ::fn_virtualfilestats output
* Notes: TimeStamp - Time at which the data was taken
* NumberReads - Number of reads issued on the file
* NumberWrites - Number of writes made on the file
* BytesRead - Number of bytes read issued on the file
* BytesWritten - Number of bytes written made on the file
* IoStallMS - Total amount of time, in milliseconds, that users
* waited for the I/Os to complete on the file
*
*
* Usage:
EXECUTE admin.dbo.GetFileStats
*
*
* Modifications:
* Developer Name Date Brief Description
* ------------------ -------- ------------------------------------------------------------
*
********************************************************************************************************/
as
set nocount on
create table #file
( Name varchar(128) not null
, FileId smallint not null
, FileName nvarchar(260)
, FileGroup varchar(128)
, Size nvarchar(18)
, MaxSize nvarchar(18)
, growth nvarchar(18)
, usage varchar(9))
if object_id('admin.dbo.FileStat','U') is null
create table admin.dbo.FileStat
(DbName varchar(128) not null
, FileName varchar(128) not null
, RecCreatedDt datetime not null
constraint dft_FileStats__RecCreatedDt
default getdate()
, PathName nvarchar(260)
, FileGroup varchar(128)
, Size nvarchar(18)
, usage varchar(9)
, TimeStamp int
, NumberReads bigint
, NumberWrites bigint
, BytesRead bigint
, BytesWritten bigint
, IoStallMS bigint
, constraint pkc_FileStats__DbName__FileName__RecCreatedDt
Primary key (DbName, FileName, RecCreatedDt))
exec sp_msforeachdb 'use ?
declare @dbid smallint
, @fileid smallint
set @dbid = db_id()
truncate table #file
insert #file
(name
, fileid
, filename
, filegroup
, size
, maxsize
, growth
, usage)
exec sp_helpfile
select @fileid = min(FileId)
from #file
while @Fileid is not null
begin
insert admin.dbo.FileStat
(DbName
, FileName
, PathName
, FileGroup
, Size
, usage
, TimeStamp
, NumberReads
, NumberWrites
, BytesRead
, BytesWritten
, IoStallMS)
SELECT db_name(@dbid)
, file_name(@fileid)
, filename
, filegroup
, size
, usage
, TimeStamp
, NumberReads
, NumberWrites
, BytesRead
, BytesWritten
, IoStallMS
FROM :: fn_virtualfilestats(@dbid,@fileid) fn
join #file f
on fn.fileid = f.fileid
select @fileid = min(FileId)
from #file
where fileid > @fileid
end'
GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO
use [admin]
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_NULLS ON
GO
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[FileStatDailyTrend]') and OBJECTPROPERTY(id, N'IsProcedure') = 1)
drop procedure [dbo].[FileStatDailyTrend]
GO
CREATE PROCEDURE [dbo].[FileStatDailyTrend]
@DbName varchar(128) = null -- all
, @FileGroup varchar(128) = null -- all
, @BeginDate datetime = 0 -- all
, @EndDate datetime = null -- thru today
AS
/*******************************************************************************************************
* dbo.FileStatDailyTrend.PRC
* Creator: Bill Wunder
*
* Description: Produce report of captured files statistics
* Notes: can specify by database and/or daterange
*
* Usage:
EXECUTE admin.dbo.FileStatDailyTrend 'admin'
*
*
* Modifications:
* Developer Name Date Brief Description
* ------------------ -------- ------------------------------------------------------------
*
********************************************************************************************************/
SET NOCOUNT ON
create table #filestat
(DbName varchar(128)
, FileName varchar(128)
, RecCreatedDt datetime
, FileGroup varchar(128)
, usage varchar(9)
, NumberReads bigint
, NumberWrites bigint
, BytesRead bigint
, BytesWritten bigint
, IoStallMS bigint
, constraint pkc_#filestat__DbName__FileName__RecCreatedDt
primary key (DbName, Filename, RecCreatedDt))
insert #filestat
(DbName
, FileName
, RecCreatedDt
, FileGroup
, usage
, NumberReads
, NumberWrites
, BytesRead
, BytesWritten
, IoStallMS)
select
DbName
, FileName
, convert(varchar(10),RecCreatedDt,102)
, FileGroup
, usage
, min(NumberReads)
, min(NumberWrites)
, min(BytesRead)
, min(BytesWritten)
, min(IoStallMS)
from admin.dbo.FileStat
where BytesRead > BytesRead -- skip bad datapoint due to restart
and DbName like isnull(@DbName,'%')
and FileGroup like isnull(@FileGroup,'%')
and (RecCreatedDt >= @BeginDate) or @BeginDate is null
and (RecCreatedDt <= @EndDate) or @EndDate is null
group by DbName
, FileName
, convert(varchar(10),RecCreatedDt,102)
, FileGroup
, usage
order by DbName
, FileName
, convert(varchar(10),RecCreatedDt,102)
, FileGroup
, usage
select f2.DbName
, f2.FileName
, f2.FileGroup
, f2.usage
, (f2.NumberReads - f1.NumberReads)/datediff(minute,f1.RecCreatedDt, f2.RecCreatedDt) [Reads/Min]
, (f2.NumberWrites - f1.NumberWrites)/datediff(minute,f1.RecCreatedDt, f2.RecCreatedDt) [Writes/Min]
, (f2.BytesRead - f1.BytesRead)/datediff(minute,f1.RecCreatedDt, f2.RecCreatedDt) [Bytes Read/Min]
, (f2.BytesWritten - f1.BytesWritten)/datediff(minute,f1.RecCreatedDt, f2.RecCreatedDt) [Bytes Written/Min]
, (f2.IoStallMS - f1.IoStallMS) * 1000 * 60/datediff(minute,f1.RecCreatedDt, f2.RecCreatedDt) [% Wait IO]
from #filestat f1
join #filestat f2
on f1.DbName = f2.DbName
and f1.FileName = F2.FileName
and convert(varchar(10),f1.RecCreatedDt,102) = convert(varchar(10),f2.RecCreatedDt - 1,102)
RETURN
GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO
Bill |