Skip to content

Added "time: ###" and "###% of ActualRows vs EstimateRows" #116

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 67 additions & 1 deletion css/qp.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,50 @@
border: 1px solid black;
}

div.qp-node-label-cost {
}

div.qp-node-label-time {
color: orange;
}

div.qp-node-label-cardinality {
color: gray;
}

span.qp-node-label-cardinality-waring {
color: red;
}

div.qp-node-label-2-warning {
background: red;
}

div.qp-node-id {
display: none;
color: gray;
position: absolute;
top: 0;
right: 0;
padding-top: 3px;
padding-right: 5px;
}

div.qp-node,
div.qp-tt {
font-size: 11px;
line-height: normal;
}

/*
div.qp-node:hover div.qp-node-id {
display: block;
}
*/
div.qp-tr:hover div.qp-node-id {
display: block;
}

.qp-statement-header {
border-color: black;
border-style: solid;
Expand Down Expand Up @@ -92,9 +130,37 @@ div[class|='qp-icon'] {
text-align: left;
}

.qp-bold,
.qp-tt-warning-important {
color: red;
}

.qp-tt-wait td {
text-align: left;
padding-left: 2px;
}

.qp-tt-wait th {
text-align: left;
}

.qp-tt-params td {
text-align: left;
padding-left: 2px;
}

.qp-tt-params th {
text-align: left;
}

.qp-tt-header {
font-weight: bold;
margin-bottom: 1px;
}

.qp-bold {
font-weight: bold;
margin-top: 7px;
margin-bottom: 1px;
}

.qp-tt-header {
Expand Down
200 changes: 190 additions & 10 deletions src/qp.xslt
Original file line number Diff line number Diff line change
Expand Up @@ -27,30 +27,93 @@
<xsl:template match="s:BatchSequence/s:Batch/s:Statements/*" mode="Statement">
<div class="qp-statement-header">
<div class="qp-statement-header-row">
<div><xsl:value-of select="@StatementText" /></div>
<div>
<xsl:attribute name="title">
<xsl:value-of select="@StatementText" />
</xsl:attribute>
<xsl:value-of select="@StatementText" />
</div>
</div>
<xsl:apply-templates select="s:QueryPlan/s:MissingIndexes/s:MissingIndexGroup" mode="MissingIndex" />
<xsl:apply-templates select="." mode="MissingIndexForEagerIndexSpool" />
</div>
<xsl:apply-templates select="." mode="QpTr" />
</xsl:template>

<xsl:template match="s:MissingIndexGroup" mode="MissingIndex">
<div class="qp-statement-header-row missing-index">
<div>Missing Index (Impact <xsl:value-of select="@Impact" />): <xsl:apply-templates select="s:MissingIndex" mode="CreateIndex" /></div>
<div>
<xsl:attribute name="title">
<xsl:apply-templates select="s:MissingIndex" mode="CreateIndex" />
</xsl:attribute>
Missing Index (Impact <xsl:value-of select="@Impact" />): <xsl:apply-templates select="s:MissingIndex" mode="CreateIndex" />
</div>
</div>
</xsl:template>

<!-- This template produces the "CREATE INDEX ..." text -->
<xsl:template match="s:MissingIndex" mode="CreateIndex">CREATE NONCLUSTERED INDEX [&lt;Name of Missing Index, sysname,>] ON <xsl:value-of select="@Schema" />.<xsl:value-of select="@Table" /> (<xsl:for-each select="s:ColumnGroup[@Usage!='INCLUDE']/s:Column">
<xsl:value-of select="@Name" />
<xsl:if test="position() != last()">,</xsl:if>
<xsl:if test="position() != last()">, </xsl:if>
</xsl:for-each>)
<xsl:if test="s:ColumnGroup[@Usage='INCLUDE']"> INCLUDE (<xsl:for-each select="s:ColumnGroup[@Usage='INCLUDE']/s:Column">
<xsl:value-of select="@Name" />
<xsl:if test="position() != last()">,</xsl:if>
<xsl:if test="position() != last()">, </xsl:if>
</xsl:for-each>)</xsl:if>
-- WITH (SORT_IN_TEMPDB=ON, DATA_COMPRESSION=PAGE)
</xsl:template>



<!-- MissingIndexForEagerIndexSpool - Match each RelOp with PhysicalOp="Index Spool" and LogicalOp="Eager Spool" -->
<xsl:template match="s:RelOp[@PhysicalOp='Index Spool' and @LogicalOp='Eager Spool']" mode="MissingIndexForEagerIndexSpool">
<div class="qp-statement-header-row missing-index">

<xsl:variable name="NodeId" select="@NodeId"/>

<!-- Extract schema and table names from the first ColumnReference in OutputList -->
<xsl:variable name="schema" select="s:OutputList/s:ColumnReference[1]/@Schema"/>
<xsl:variable name="table" select="s:OutputList/s:ColumnReference[1]/@Table"/>

<!-- Extract key columns from SeekPredicate in the Spool operation -->
<!-- <xsl:variable name="keyColumns" select="s:Spool/s:SeekPredicateNew/s:SeekKeys/s:Prefix/s:RangeExpressions/s:ScalarOperator/s:Identifier/s:ColumnReference/@Column"/>-->
<xsl:variable name="keyColumns" select="s:Spool/s:SeekPredicateNew/s:SeekKeys/s:Prefix/s:RangeColumns/s:ColumnReference/@Column"/>

<!-- Extract included columns from OutputList in the Spool operation -->
<xsl:variable name="includeColumns" select="s:OutputList/s:ColumnReference/@Column"/>

<!-- Generate SQL CREATE INDEX statement with schema, table, key columns, and included columns -->
<!-- <xsl:text>Missing Index (for "Eager Index Spool", at nodeId=</xsl:text>-->
<xsl:text>Found "Eager Index Spool", at nodeId=</xsl:text>
<xsl:value-of select="$NodeId"/>
<xsl:text>: </xsl:text>
<xsl:text>CREATE NONCLUSTERED INDEX &lt;Name of Missing Index&gt; ON </xsl:text>
<xsl:value-of select="$schema"/>
<xsl:text>.</xsl:text>
<xsl:value-of select="$table"/>
<xsl:text> (</xsl:text>
<xsl:for-each select="$keyColumns">
<xsl:value-of select="."/>
<xsl:if test="position() != last()">, </xsl:if>
</xsl:for-each>
<xsl:text>)</xsl:text>

<!-- Include additional columns if any are found in the OutputList -->
<xsl:if test="count($includeColumns) &gt; 0">
<xsl:text>&#10;INCLUDE (</xsl:text>
<xsl:for-each select="$includeColumns">
<xsl:value-of select="."/>
<xsl:if test="position() != last()">, </xsl:if>
</xsl:for-each>
<xsl:text>)</xsl:text>
</xsl:if>
<xsl:text>&#10;</xsl:text>
<xsl:text>-- WITH (SORT_IN_TEMPDB=ON, DATA_COMPRESSION=PAGE) &#10;</xsl:text>

</div>
</xsl:template>



<!-- Each node has a parent qp-tr element which contains / positions the node and its children -->
<xsl:template match="s:RelOp|s:StmtSimple|s:StmtUseDb|s:StmtCond|s:StmtCursor|s:Operation" mode="QpTr">
<div class="qp-tr">
Expand All @@ -64,9 +127,12 @@
<xsl:attribute name="data-node-id"><xsl:value-of select="@NodeId" /></xsl:attribute>
</xsl:if>
<xsl:call-template name="NodeIcon" />
<div class="qp-node-id"><xsl:value-of select="@NodeId" /></div>
<div><xsl:apply-templates select="." mode="NodeLabel" /></div>
<xsl:apply-templates select="." mode="NodeLabel2" />
<xsl:apply-templates select="." mode="NodeCostLabel" />
<xsl:apply-templates select="." mode="NodeTimeLabel" />
<xsl:apply-templates select="." mode="NodeCardinalityLabel" />
<xsl:call-template name="ToolTip" />
</div>
</div>
Expand Down Expand Up @@ -315,6 +381,11 @@
<xsl:with-param name="Value" select="@NodeId" />
</xsl:call-template>

<xsl:call-template name="ToolTipRow">
<xsl:with-param name="Label">Non Parallel Plan Reason</xsl:with-param>
<xsl:with-param name="Value" select="s:QueryPlan/@NonParallelPlanReason" />
</xsl:call-template>

</table>
</xsl:template>

Expand Down Expand Up @@ -412,14 +483,77 @@
<xsl:template match="s:RelOp" mode="NodeCostLabel">
<xsl:variable name="EstimatedOperatorCost"><xsl:call-template name="EstimatedOperatorCost" /></xsl:variable>
<xsl:variable name="TotalCost"><xsl:value-of select="ancestor::s:QueryPlan/s:RelOp/@EstimatedTotalSubtreeCost" /></xsl:variable>
<div>Cost: <xsl:value-of select="format-number(number($EstimatedOperatorCost) div number($TotalCost), '0%')" /></div>
<div class="qp-node-label-cost">Cost: <xsl:value-of select="format-number(number($EstimatedOperatorCost) div number($TotalCost), '0%')" /></div>
</xsl:template>

<!-- Dont show the node cost for statements. -->
<xsl:template match="s:StmtSimple|s:StmtUseDb" mode="NodeCostLabel" />

<xsl:template match="s:StmtCursor|s:Operation|s:StmtCond" mode="NodeCostLabel">
<div>Cost: 0%</div>
<div class="qp-node-label-cost">Cost: 0%</div>
</xsl:template>

<!-- Displays the node TIME label. -->
<xsl:template match="s:RelOp" mode="NodeTimeLabel">
<xsl:if test="s:RunTimeInformation/s:RunTimeCountersPerThread/@ActualElapsedms">
<!-- <xsl:variable name="NodeTimeMs"><xsl:value-of select="max(s:RunTimeInformation/s:RunTimeCountersPerThread/@ActualElapsedms)" /></xsl:variable>-->
<!-- get MAX value for 'ActualElapsedms', if we are using a Parallel Plan -->
<xsl:variable name="NodeTimeMs">
<xsl:for-each select="s:RunTimeInformation/s:RunTimeCountersPerThread/@ActualElapsedms">
<xsl:sort select="number(.)" data-type="number" order="descending"/>
<xsl:if test="position() = 1">
<xsl:value-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<div class="qp-node-label-time">Time: <xsl:value-of select="format-number(number($NodeTimeMs) div number(1000), '0.000')" /> s</div>
</xsl:if>
</xsl:template>

<!-- Displays the node TIME label (at Statement level). -->
<xsl:template match="s:StmtSimple" mode="NodeTimeLabel">
<xsl:if test="s:QueryPlan/s:QueryTimeStats/@ElapsedTime">
<xsl:variable name="ElapsedTime"> <xsl:value-of select="s:QueryPlan/s:QueryTimeStats/@ElapsedTime" /></xsl:variable>
<xsl:variable name="CpuTime"> <xsl:value-of select="s:QueryPlan/s:QueryTimeStats/@CpuTime" /></xsl:variable>
<xsl:variable name="UdfElapsedTime"><xsl:value-of select="s:QueryPlan/s:QueryTimeStats/@UdfElapsedTime" /></xsl:variable>
<xsl:variable name="UdfCpuTime"> <xsl:value-of select="s:QueryPlan/s:QueryTimeStats/@UdfCpuTime" /></xsl:variable>
<div class="qp-node-label-time">
Time: <xsl:value-of select="format-number(number($ElapsedTime) div number(1000), '0.000')" /> s
<br />
CPU Time: <xsl:value-of select="format-number(number($CpuTime) div number(1000), '0.000')" /> s
<br />
<xsl:if test="not($UdfElapsedTime = '')">
UDF Time: <xsl:value-of select="format-number(number($UdfElapsedTime) div number(1000), '0.000')" /> s
<br />
</xsl:if>
<xsl:if test="not($UdfCpuTime = '')">
UDF CPU Time: <xsl:value-of select="format-number(number($UdfCpuTime) div number(1000), '0.000')" /> s
<br />
</xsl:if>
</div>
</xsl:if>
</xsl:template>

<!-- Displays the node CARDINALITY label. -->
<xsl:template match="s:RelOp" mode="NodeCardinalityLabel">
<xsl:if test="s:RunTimeInformation/s:RunTimeCountersPerThread/@ActualRows">
<xsl:variable name="EstimateRows"><xsl:value-of select="@EstimateRows" /></xsl:variable>
<xsl:variable name="ActualRows"><xsl:value-of select="sum(s:RunTimeInformation/s:RunTimeCountersPerThread/@ActualRows)" /></xsl:variable>
<xsl:variable name="CardinalityPct"><xsl:value-of select="number($ActualRows) div number($EstimateRows)" /></xsl:variable>
<div class="qp-node-label-cardinality">
A=<xsl:value-of select="format-number(number($ActualRows), '###,###,###,###')"/> of
<br />
E=<xsl:value-of select="format-number(number($EstimateRows), '###,###,###,###')"/>
<span>
<!-- if Cardinality is 100 times higher ($CardinalityPct is 1.0 at 100%) than expected, them MARK it as a WARNING -->
<!-- possibly add 'and $ActualRows &gt; 10000' to discard warning if "not that many actual rows" -->
<xsl:if test="$CardinalityPct &gt; 100 and $ActualRows &gt; 5000">
<xsl:attribute name="class">qp-node-label-cardinality-waring</xsl:attribute>
</xsl:if>
(<xsl:value-of select="format-number($CardinalityPct, '0%')"/>)
</span>
</div>
</xsl:if>
</xsl:template>

<!--
Expand Down Expand Up @@ -507,7 +641,7 @@
Seek Predicates Tooltip
-->

<xsl:template match="s:SeekPredicates" mode="ToolTipDetails">
<xsl:template match="s:SeekPredicates | s:Spool" mode="ToolTipDetails">
<div class="qp-bold">Seek Predicates</div>
<div>
<xsl:for-each select="s:SeekPredicateNew/s:SeekKeys">
Expand Down Expand Up @@ -537,7 +671,8 @@
<div>Columns With No Statistics: <xsl:apply-templates select="." mode="ObjectNameNoAlias" /></div>
</xsl:for-each>
<xsl:for-each select="s:Warnings/s:Wait">
<div>The query had to wait <xsl:value-of select="@WaitTime" /> seconds for <xsl:value-of select="@WaitType" /> during execution.</div>
<div class="qp-tt-warning-important">The query had to wait <xsl:value-of select="@WaitTime" /> seconds for <xsl:value-of select="@WaitType" /> during execution.</div>
<!-- <div>The query had to wait <xsl:value-of select="@WaitTime" /> seconds for <xsl:value-of select="@WaitType" /> during execution.</div>-->
</xsl:for-each>
<xsl:for-each select="s:Warnings/s:PlanAffectingConvert">
<div>Type conversion in expression (<xsl:value-of select="@Expression" />) may affect "<xsl:value-of select="@ConvertIssue" />" in query plan choice.</div>
Expand All @@ -553,8 +688,46 @@
</xsl:for-each>
</div>
</xsl:if>

<!-- Wait Statistics (at the Statement level), IF Available -->
<xsl:if test="s:WaitStats">
<div class="qp-bold">Top 10 Waits</div>
<div class="qp-tt-wait">
<table>
<thead><tr> <th>Type</th> <th>Ms</th> <th>Count</th> <th>Ms/Count</th> </tr></thead>
<tbody>
<xsl:for-each select="s:WaitStats/s:Wait">
<xsl:sort select="number(@WaitTimeMs)" data-type="number" order="descending"/>
<tr>
<td><xsl:value-of select="@WaitType" /></td>
<td><xsl:value-of select="@WaitTimeMs" /></td>
<td><xsl:value-of select="@WaitCount" /></td>
<td><xsl:value-of select="format-number(number(@WaitTimeMs) div number(@WaitCount), '0.###')" /></td>
</tr>
</xsl:for-each>
</tbody>
</table>
</div>
</xsl:if>

<!-- Parameters Compile and Runtime (at the Statement level), IF Available -->
<xsl:if test="s:ParameterList">
<div class="qp-bold">Compile and Runtime Parameters</div>
<div class="qp-tt-params">
<table>
<thead> <tr> <th>Name</th> <th>DataType</th> <th>Compiled Value</th> <th>Runtime Value</th> </tr> </thead>
<tbody>
<xsl:for-each select="s:ParameterList/s:ColumnReference">
<xsl:sort select="@Column"/>
<tr> <td><xsl:value-of select="@Column" /></td> <td><xsl:value-of select="@ParameterDataType" /></td> <td><xsl:value-of select="@ParameterCompiledValue" /></td> <td><xsl:value-of select="@ParameterRuntimeValue" /></td> </tr>
</xsl:for-each>
</tbody>
</table>
</div>
</xsl:if>
</xsl:template>


<xsl:template name="SeekKeyDetail">
<xsl:param name="position" />Seek Keys[<xsl:value-of select="$position" />]: <xsl:for-each select="s:Prefix|s:StartRange|s:EndRange">
<xsl:choose>
Expand Down Expand Up @@ -677,7 +850,14 @@

<!-- Display the logical operation for any node where it is not the same as the physical operation. -->
<xsl:template match="s:RelOp[@LogicalOp != @PhysicalOp]" mode="NodeLabel2">
<div>(<xsl:value-of select="@LogicalOp" />)</div>
<xsl:choose>
<xsl:when test="@PhysicalOp = 'Index Spool' and @LogicalOp = 'Eager Spool'">
<div class="qp-node-label-2-warning">(<xsl:value-of select="@LogicalOp" />)</div>
</xsl:when>
<xsl:otherwise>
<div>(<xsl:value-of select="@LogicalOp" />)</div>
</xsl:otherwise>
</xsl:choose>
</xsl:template>

<!-- Disable the default template -->
Expand Down