High-Fragmented Tables Diagnostic Probe Action

Microsoft.SQLServerAppliance.MDW.FragmentedIndexesDiagnosticProbe (ProbeActionModuleType)

High-Fragmented Tables Diagnostic Probe Action.

Element properties:

TypeProbeActionModuleType
IsolationAny
AccessibilityInternal
RunAsMicrosoft.SQLServerAppliance.MDW.MonitoringProfile
InputTypeSystem.BaseData
OutputTypeSystem.CommandOutput

Member Modules:

ID Module Type TypeId RunAs 
Command ProbeAction Microsoft.Windows.ScriptProbeAction Default

Overrideable Parameters:

IDParameterTypeSelectorDisplay NameDescription
TimeoutSecondsint$Config/TimeoutSeconds$Timeout (sec)

Source Code:

<ProbeActionModuleType ID="Microsoft.SQLServerAppliance.MDW.FragmentedIndexesDiagnosticProbe" Accessibility="Internal" RunAs="MDW!Microsoft.SQLServerAppliance.MDW.MonitoringProfile" PassThrough="false" Batching="false">
<Configuration>
<xsd:element name="ConnectionString" type="xsd:string"/>
<xsd:element name="DataBaseName" type="xsd:string"/>
<xsd:element name="TablesCount" type="xsd:integer"/>
<xsd:element name="TableSize" type="xsd:double"/>
<xsd:element name="Threshold" type="xsd:double"/>
<xsd:element name="TimeoutSeconds" type="xsd:integer"/>
</Configuration>
<OverrideableParameters>
<OverrideableParameter ID="TimeoutSeconds" ParameterType="int" Selector="$Config/TimeoutSeconds$"/>
</OverrideableParameters>
<ModuleImplementation>
<Composite>
<MemberModules>
<ProbeAction ID="Command" TypeID="Windows!Microsoft.Windows.ScriptProbeAction">
<ScriptName>SQLTableFragmentedIndexes.vbs</ScriptName>
<Arguments>"$Config/ConnectionString$" "$Config/DataBaseName$" "$Config/TablesCount$" "$Config/TableSize$" "$Config/Threshold$"</Arguments>
<ScriptBody><Script>
' ##### ..\Scripts\Common\Common.vbs
Option Explicit
SetLocale("en-us")

Dim USE_EVENT_LOG, USE_ERROR_LOG

Const DEBUG_MODE = false
Const DEBUG_SCRIPT_EVENT_ID = 4201

Const SCRIPT_EVENT_ID = 4200

USE_EVENT_LOG = true
USE_ERROR_LOG = false


Const ManagementGroupName = "$Target/ManagementGroup/Name$"


Sub HandleError(ByVal customMessage)
Dim logger
If Err.number &lt;&gt; 0 Then
Set logger = new ScriptLogger
logger.LogError(customMessage)
If Not USE_ERROR_LOG Then
Wscript.Quit 0
Else
Wscript.Quit 1
End If
End If
End Sub

Sub HandleErrorInMonitoring(ByVal customMessage, ByRef oBug, ByRef oAPI)
Dim logger
If Err.number &lt;&gt; 0 Then
Set logger = new ScriptLogger
logger.LogError(customMessage)
Call oAPI.Return(oBag)
Wscript.Quit 0
End If
End Sub

Sub HandleErrorInDiscovery(ByVal customMessage, ByRef oDiscovery, ByRef oAPI)
Dim logger
If Err.number &lt;&gt; 0 Then
Set logger = new ScriptLogger
logger.LogError(customMessage)
oDiscovery.IsSnapshot = false
HandleDebugMessage("Discovery: IsSnapshot = false")
oAPI.Return(oDiscoveryData)
Wscript.Quit 0
End If
End Sub

Sub HandleDebugMessage(ByVal debugMessage)
Dim logger
Set logger = new ScriptLogger
logger.LogDebug(debugMessage)
End Sub


Const ERROR_EVENT_TYPE = 1
Const WARNING_EVENT_TYPE = 2
Const INFO_EVENT_TYPE = 4

Class ScriptLogger
Dim sourceLogEvent
Dim oAPI

Private Sub Class_Initialize()
sourceLogEvent = "Managegement Group: " + ManagementGroupName + ". Script: " + WScript.ScriptName
Set oAPI = CreateObject("MOM.ScriptAPI")
End Sub

Private Function LogEvent(ByVal message, ByVal eventType, ByVal scriptEventID)
On Error Resume Next
Call oAPI.LogScriptEvent(sourceLogEvent, scriptEventID, eventType, message)
End Function

Public Function LogDebug(ByVal message)
if DEBUG_MODE Then
WScript.StdOut.WriteLine message
Call oAPI.LogScriptEvent(sourceLogEvent, DEBUG_SCRIPT_EVENT_ID, INFO_EVENT_TYPE, message)
End If
End Function

Public Function LogWarning(ByVal message, ByVal eventID)
if DEBUG_MODE Then
WScript.StdOut.WriteLine message
End If
LogEvent message, WARNING_EVENT_TYPE, eventID
End Function

Public Function LogError(ByVal customMessage)
Dim message
If Err.number &lt;&gt; 0 Then
message = Replace(" Error Number: #P1# " &amp; VbCrLf &amp; " Description: #P2# ", "#P1#", CStr(Err.number and 65535) )
message = Replace(message, "#P2#", Err.Description )
message = customMessage &amp; VbCrLf &amp; message &amp; VbCrLf
if DEBUG_MODE Then
WScript.StdOut.WriteLine message
End If

If USE_EVENT_LOG Then
LogEvent message, ERROR_EVENT_TYPE, SCRIPT_EVENT_ID
End If
If USE_ERROR_LOG Then
WScript.StdErr.WriteLine message
End If
End If
End Function

End Class

Function IsStringEmpty(ByVal sValue)
If sValue = EMPTY Or Trim(sValue) = "" Then
IsStringEmpty = true
Else
IsStringEmpty = false
End If
End Function
' ##### ..\Scripts\Common\AdoDB.vbs

'This class is wrapper for ADODB typical operations (Connect and Run SQL query)
Class SQLQuery
PRIVATE adodbConnection

Private Sub Class_Initialize()
On Error Resume Next
Set adodbConnection = CreateObject("ADODB.Connection")
Call HandleError("Cannot create ADODB.Connection")
End Sub

Private Sub Class_Terminate()
On Error Resume Next

'adodbConnection.State = 0 - Closed
If adodbConnection.State &gt; 0 Then
Call adodbConnection.Close()
End If

Call HandleError("Cannot close ADODB.Connection")
End Sub
'This method create and open connection to sql Server
'&lt;connectionString&gt; - name of the server and instance, e.g. "server1.mydomain.com\Instance1";
'use only server name to connect to default instance
Public Sub Connect(ByVal connectionString, ByVal databaseName)
On Error Resume Next
Dim providerString
providerString = "Provider=SQLOLEDB;Server=" &amp; EscapeConnStringValue(connectionString) &amp; ";Database="&amp; EscapeConnStringValue(databaseName) &amp;";Trusted_Connection=yes"
Call adodbConnection.Open(providerString)
End Sub

'This function execute &lt;query&gt; with &lt;parameters&gt; and returns ADODB.RecordSet
'&lt;parameters&gt; must be array, and must be with the same order as sql query &lt;?&gt; paremetrs
Public Function ExecuteQueryToRecordSet(ByVal query, ByVal parameters)
Dim adodbCommand, oResults
Set adodbCommand = CreateObject("ADODB.Command")
Call HandleError("Cannot create ADODB.Command")
adodbCommand.ActiveConnection = adodbConnection
adodbCommand.CommandText = query
Dim i, dataType, dataSize, parameter, length
length = UBound(parameters)
'Put parameters in to ADODB.Command
For i=0 To length
parameter = parameters(i)
dataType = GetDataTypeEnum(parameter)
dataSize = GetDataTypeSize(parameter)
adodbCommand.Parameters.Append(adodbCommand.CreateParameter("p"&amp;i, dataType, 1, dataSize, parameter)) '1 - this is adParamInput in ADODB. see http://www.w3schools.com/ado/met_comm_createparameter.asp#parameterdirenum
Next
Set ExecuteQueryToRecordSet = adodbCommand.Execute()
End Function

'returns size of string or 0 for others types
Private Function GetDataTypeSize(ByVal vValue)
If TypeName(vValue)="String" Then
GetDataTypeSize = Len(vValue)
Else
GetDataTypeSize = 0
End If
End Function

'this function returns int value of some DataTypeEnum
'values of adodb types see on http://www.w3schools.com/ado/met_comm_createparameter.asp#datatypeenum
Private Function GetDataTypeEnum(ByVal vValue)
If TypeName(vValue) = "Byte" Then
GetDataTypeEnum = 2 ' adSmallInt=2
End If
If TypeName(vValue)="Integer" or TypeName(vValue)="Long" Then
GetDataTypeEnum = 3 ' adInteger=3
End If
If TypeName(vValue)="Double" Then
GetDataTypeEnum = 5 ' adDouble=5
End If
If TypeName(vValue)="String" Then
GetDataTypeEnum = 8 ' adBSTR = 8
End If
End Function

'This function escapes string value for connection string
Private Function EscapeConnStringValue (ByVal strValue)
On Error Resume Next
EscapeConnStringValue = "{" + Replace(strValue, "}", "}}") + "}"
End Function

End Class
' ##### Scripts\SQLTableFragmentedIndexes.vbs

On Error Resume Next

Const SCAN_MODE = "SAMPLED" 'DEFAULT, NULL, LIMITED, SAMPLED &amp; DETAILED

DIM SQL_QUERY
'Main SQL query &lt;?&gt;-parametr
SQL_QUERY = "WITH FragmentedIndexes (table_id, table_size_in_mb, avg_fragment_size_in_pages) " &amp; _
" as " &amp; _
" ( " &amp; _
" select Top(?) " &amp; _
" object_id, " &amp; _
" page_count * 8/1024, " &amp; _
" avg_fragment_size_in_pages " &amp; _
" from " &amp; _
" sys.dm_db_index_physical_stats (DB_ID(?), OBJECT_ID('$(TABLENAME)'), NULL, NULL, ?) " &amp; _
" where index_type_desc = 'CLUSTERED INDEX' " &amp; _
" and index_level = 0 " &amp; _
" and avg_fragment_size_in_pages &gt; 0 " &amp; _
" order by page_count desc, avg_fragment_size_in_pages " &amp; _
" ) " &amp; _
" select " &amp; _
" t.name, " &amp; _
" i.name, " &amp; _
" pis.table_size_in_mb, " &amp; _
" pis.avg_fragment_size_in_pages " &amp; _
" from " &amp; _
" FragmentedIndexes pis " &amp; _
" inner join sys.tables t " &amp; _
" on pis.table_id = t.object_id " &amp; _
" inner join sys.indexes i " &amp; _
"on t.object_id = i.object_id " &amp; _
"and i.[type] = 1 " &amp; _
" where " &amp; _
" pis.table_size_in_mb &gt;= ? " &amp; _
" and pis.avg_fragment_size_in_pages &lt;= ? " &amp; _
" order by avg_fragment_size_in_pages "


'Dim logger
'Set logger = new ScriptLogger

Call Main()

'Build string fixed length aligned right
Function RightAlignFixedString(ByVal sValue, ByVal nWidth)
If Len(sValue) &gt; nWidth Then
RightAlignFixedString = Left(sValue, nWidth)
ElseIf Len(sValue) &lt; nWidth-1 Then
Dim i, length, sResult
length = nWidth - Len(sValue)-1
sResult = sValue
For i=0 to length
sResult = " " &amp; sResult
Next
RightAlignFixedString = sResult
End If
End Function

'Build string fixed length aligned left
Function LeftAlignFixedString(ByVal sValue, ByVal nWidth)
If Len(sValue) &gt; nWidth+1 Then
LeftAlignFixedString = Left(sValue, nWidth+1)
ElseIf Len(sValue) &lt; nWidth Then
Dim i, length, sResult
length = nWidth - Len(sValue)
sResult = sValue
For i=0 to length
sResult = sResult &amp; " "
Next
LeftAlignFixedString = sResult
End If
End Function

'This function connect to sql server and run sql query for calculating count of fragmented indexes per DB
Sub GetFragmentedIndexes(ByVal sConnectionString, ByVal sDataBaseName, ByVal nTop, ByVal nMaxTableSize, ByVal nThreshold)
On Error Resume Next

Dim oSqlQry, oRecorSet
Dim sTableName, sIndexName, nTableSize, nFragmentCount
Set oSqlQry = new SQLQuery

Call oSqlQry.Connect(sConnectionString, sDataBaseName)
Call WriteErrorIfOccured("Cannot connect to the database on the " &amp; sConnectionString)
Set oRecorSet = oSqlQry.ExecuteQueryToRecordSet(SQL_QUERY, Array(nTop, sDataBaseName, SCAN_MODE, nMaxTableSize, nThreshold))
Call WriteErrorIfOccured("Cannot execute query")
WScript.Echo " Fragmented Tables"
WScript.Echo " -------------------"
WScript.Echo
WScript.Echo " Connection String: ", sConnectionString
WScript.Echo " Database Name: ", sDataBaseName
WScript.Echo
WScript.Echo "+------------------------------------+--------------+-----------------+--------------------------------------------------------------------------------------+"
WScript.Echo "| | Table Size |Average Fragment | |"
WScript.Echo "| Table Name | (MB) | size (pages) | Index Name |"
WScript.Echo "+------------------------------------+--------------+-----------------+--------------------------------------------------------------------------------------+"
If Not IsEmpty(oRecorSet) Then
Do While Not oRecorSet.EOF
sTableName = oRecorSet(0)
sIndexName = oRecorSet(1)
nTableSize = oRecorSet(2)
nFragmentCount = oRecorSet(3)
Call WriteErrorIfOccured("Cannot read data from record set")
WScript.Echo "|", LeftAlignFixedString(sTableName, 33) , "|" , RightAlignFixedString(Round(nTableSize,2), 12) , "|" , RightAlignFixedString(Round(nFragmentCount,2), 15), "|" , LeftAlignFixedString(sIndexName, 83) , "|"
Call oRecorSet.MoveNext()
Loop
End If
WScript.Echo "+------------------------------------+--------------+-----------------+--------------------------------------------------------------------------------------+"
End Sub


Sub Main()
On Error Resume Next
Dim sConnectionString, sDataBaseName, nTableSize, nTop, nThreshold

Dim oParams
Set oParams = WScript.Arguments


sConnectionString = oParams(0)
Call WriteErrorIfOccured("Cannot read ConnectionString from params")

sDataBaseName = oParams(1)
Call WriteErrorIfOccured("Cannot read DataBaseName from params")


nTop = cint(oParams(2))
Call WriteErrorIfOccured("Cannot read Top from params")


nTableSize = cdbl(oParams(3))
Call WriteErrorIfOccured("Cannot read TableSize from params")

nThreshold = cdbl(oParams(4))
Call WriteErrorIfOccured("Cannot read Threshold from params")


Call GetFragmentedIndexes(sConnectionString, sDataBaseName, nTop, nTableSize, nThreshold)
End Sub

Sub WriteErrorIfOccured(text)
If Err.number &lt;&gt; 0 Then
WScript.Echo "Error: " &amp; text
WScript.Quit 0
End If
End Sub

</Script></ScriptBody>
<TimeoutSeconds>$Config/TimeoutSeconds$</TimeoutSeconds>
</ProbeAction>
</MemberModules>
<Composition>
<Node ID="Command"/>
</Composition>
</Composite>
</ModuleImplementation>
<OutputType>System!System.CommandOutput</OutputType>
<InputType>System!System.BaseData</InputType>
</ProbeActionModuleType>