Skip to content

Commit

Permalink
Query store top queries improvement
Browse files Browse the repository at this point in the history
Option to group by object.
Include interval start/end times.
Column picker.
  • Loading branch information
DavidWiseman committed Jul 7, 2024
1 parent 490dec1 commit c9b9c45
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 43 deletions.
25 changes: 13 additions & 12 deletions DBADash/Messaging/QueryStoreTopQueriesMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,12 @@ public enum QueryStoreGroupByEnum
query_id,
plan_id,
query_hash,
query_plan_hash
query_plan_hash,
object_id
}

public bool NearestInterval { get; set; } = true;

private async Task<int> GetIdleSchedulerCount(string connectionString)
{
await using var cn = new SqlConnection(connectionString);
Expand All @@ -72,9 +73,9 @@ private async Task<int> GetThreadCountBasedOnSchedulerAvailability(string connec
try
{
idleSchedulerCount = await GetIdleSchedulerCount(connectionString);
Log.Debug("Idle scheduler count {idleSchedulerCount}",idleSchedulerCount);
Log.Debug("Idle scheduler count {idleSchedulerCount}", idleSchedulerCount);
}
catch(Exception ex)
catch (Exception ex)
{
Log.Error(ex, "Error getting idle scheduler count");
}
Expand Down Expand Up @@ -137,23 +138,23 @@ public override async Task<DataSet> Process(CollectionConfig cfg, Guid handle)
{
databases = new List<string> { DatabaseName };
}
Log.Debug("Collecting Query Store for databases: {databases} for message {handle}", databases,handle);
Log.Debug("Collecting Query Store for databases: {databases} for message {handle}", databases, handle);
var threadCount = 1;
switch (databases.Count)
{
case 0:
throw new Exception("No databases found with Query Store enabled");
case 1:
break;

default:
threadCount = await GetThreadCountBasedOnSchedulerAvailability(src.SourceConnection.ConnectionString);
Log.Information("Processing message {handle} with thread count {maxDegreeOfParallelism}",handle, threadCount);
Log.Information("Processing message {handle} with thread count {maxDegreeOfParallelism}", handle, threadCount);
break;
}
var resultTable = new DataTable();


var options = new ParallelOptions { MaxDegreeOfParallelism = threadCount};

var options = new ParallelOptions { MaxDegreeOfParallelism = threadCount };

// Use a concurrent bag to collect DataTables from parallel tasks.
var dataTables = new ConcurrentBag<DataTable>();
Expand Down Expand Up @@ -236,7 +237,7 @@ private async Task<DataTable> GetTopQueriesForDatabase(string connectionString,
{
await using var cn = new SqlConnection(connectionString);
await cn.OpenAsync();
await using var cmd = new SqlCommand(SqlStrings.QueryStoreTopQueries, cn) {CommandTimeout = Lifetime};
await using var cmd = new SqlCommand(SqlStrings.QueryStoreTopQueries, cn) { CommandTimeout = Lifetime };
cmd.Parameters.AddWithValue("@Database", db);
cmd.Parameters.AddWithValue("@FromDate", From);
cmd.Parameters.AddWithValue("@ToDate ", To);
Expand All @@ -246,9 +247,9 @@ private async Task<DataTable> GetTopQueriesForDatabase(string connectionString,
cmd.Parameters.AddWithValue("@ObjectID", ObjectID is > 0 ? ObjectID.Value : DBNull.Value);
cmd.Parameters.AddWithValue("@ObjectName", string.IsNullOrEmpty(ObjectName) ? DBNull.Value : ObjectName);
cmd.Parameters.AddWithValue("@GroupBy", GroupBy.ToString());
var pQueryHash = cmd.Parameters.Add("@QueryHash", SqlDbType.Binary, 8);
var pQueryHash = cmd.Parameters.Add("@QueryHash", SqlDbType.Binary, 8);
pQueryHash.Value = QueryHash is not null ? QueryHash : DBNull.Value;
var pPlanHash = cmd.Parameters.Add("@QueryPlanHash", SqlDbType.Binary,8);
var pPlanHash = cmd.Parameters.Add("@QueryPlanHash", SqlDbType.Binary, 8);
pPlanHash.Value = QueryPlanHash is not null ? QueryPlanHash : DBNull.Value;
cmd.Parameters.AddWithValue("@QueryID", QueryID is > 0 ? QueryID.Value : DBNull.Value);
cmd.Parameters.AddWithValue("@PlanID", PlanID is > 0 ? PlanID.Value : DBNull.Value);
Expand Down
16 changes: 11 additions & 5 deletions DBADash/SQL/SQLQueryStoreTopQueries.sql
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ BEGIN
RAISERROR('Invalid database',11,1)
RETURN
END
IF @GroupBy NOT IN('query_id','plan_id','query_plan_hash','query_hash')
IF @GroupBy NOT IN('query_id','plan_id','query_plan_hash','query_hash','object_id')
BEGIN
RAISERROR('Invalid group by',11,1)
RETURN
Expand Down Expand Up @@ -70,13 +70,15 @@ SELECT TOP (@Top)
DB_NAME() AS DB,
',CASE WHEN @GroupBy = 'plan_id' THEN 'RS.plan_id,P.query_plan_hash,' ELSE '' END,'
',CASE WHEN @GroupBy IN('query_id', 'plan_id') THEN '
P.query_id query_id,
Q.object_id object_id,
P.query_id,
Q.object_id,
Q.query_hash,
ISNULL(OBJECT_NAME(Q.object_id),'''') object_name,
QT.query_sql_text query_sql_text,'
WHEN @GroupBy = 'query_hash' THEN 'Q.query_hash,'
WHEN @GroupBy = 'query_plan_hash' THEN 'P.query_plan_hash,'
WHEN @GroupBy = 'object_id' THEN 'Q.object_id,
ISNULL(OBJECT_NAME(Q.object_id),'''') object_name,'
ELSE NULL END,
CASE WHEN @IncludeWaits = 1 THEN 'STUFF(
(SELECT TOP(3) '', '' + CONCAT(W.wait_category_desc, '' = '',SUM(W.total_query_wait_time_ms),''ms'')
Expand Down Expand Up @@ -113,8 +115,10 @@ SELECT TOP (@Top)
', CASE WHEN COLUMNPROPERTY(OBJECT_ID('sys.query_store_runtime_stats'),'avg_tempdb_space_used','ColumnId') IS NULL THEN '' ELSE 'SUM(RS.avg_tempdb_space_used*RS.count_executions)/NULLIF(SUM(RS.count_executions),0)*8 AS avg_tempdb_space_used_kb,' END,'
', CASE WHEN COLUMNPROPERTY(OBJECT_ID('sys.query_store_runtime_stats'),'max_tempdb_space_used','ColumnId') IS NULL THEN '' ELSE 'MAX(RS.max_tempdb_space_used)*8 AS max_tempdb_space_used_kb,' END + '
MAX(RS.max_dop) AS max_dop,
', CASE WHEN @GroupBy IN('query_id''plan_id') THEN 'Q.query_parameterization_type_desc,' ELSE '' END, '
', CASE WHEN @GroupBy = 'plan_id' THEN 'P.plan_forcing_type_desc,P.force_failure_count,P.last_force_failure_reason_desc,P.is_parallel_plan' ELSE 'COUNT(DISTINCT P.plan_id) num_plans' END, '
', CASE WHEN @GroupBy IN('query_id','plan_id') THEN 'Q.query_parameterization_type_desc,' ELSE 'COUNT(DISTINCT Q.query_id) AS num_queries,' END, '
', CASE WHEN @GroupBy = 'plan_id' THEN 'P.plan_forcing_type_desc,P.force_failure_count,P.last_force_failure_reason_desc,P.is_parallel_plan,' ELSE 'COUNT(DISTINCT P.plan_id) num_plans,' END, '
MIN(MIN(RS.first_execution_time)) OVER() interval_start,
MAX(MAX(RS.last_execution_time)) OVER() interval_end
FROM sys.query_store_runtime_stats AS RS
JOIN sys.query_store_plan AS P ON P.plan_id = RS.plan_id
JOIN sys.query_store_query AS Q ON Q.query_id = P.query_id
Expand All @@ -129,9 +133,11 @@ AND RS.runtime_stats_interval_id <= @interval_to
', CASE WHEN @QueryHash IS NOT NULL THEN 'AND Q.query_hash = @QueryHash' ELSE '' END,'
', CASE WHEN @QueryPlanHash IS NOT NULL THEN 'AND P.query_plan_hash = @QueryPlanHash' ELSE '' END,'
', CASE WHEN @ParallelPlans = 1 THEN 'AND P.is_parallel_plan = 1' ELSE '' END,'
', CASE WHEN @GroupBy = 'object_id' THEN 'AND Q.object_id <> 0' ELSE '' END, '
GROUP BY ',CASE WHEN @GroupBy = 'query_id' THEN 'P.query_id, QT.query_sql_text, Q.object_id,Q.query_hash,Q.query_parameterization_type_desc'
WHEN @GroupBy = 'query_plan_hash' THEN 'P.query_plan_hash'
WHEN @GroupBy = 'query_hash' THEN 'Q.query_hash'
WHEN @GroupBy = 'object_id' THEN 'Q.object_id'
WHEN @GroupBy = 'plan_id' THEN 'P.query_id, QT.query_sql_text, Q.object_id,Q.query_hash,Q.query_parameterization_type_desc,RS.plan_id,P.query_plan_hash,P.plan_forcing_type_desc,P.force_failure_count,P.last_force_failure_reason_desc,P.is_parallel_plan'
ELSE NULL END, '
', CASE WHEN @MinimumPlanCount >1 THEN 'HAVING COUNT(DISTINCT RS.plan_id)>=@MinimumPlanCount' ELSE '' END,'
Expand Down
21 changes: 17 additions & 4 deletions DBADashGUI/DateHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public static DataTable ConvertUTCToAppTimeZone(ref DataTable dt, List<string> c
{
foreach (DataColumn col in dt.Columns)
{
if (col.DataType == typeof(DateTime))
if (col.DataType == typeof(DateTime) || col.DataType == typeof(DateTimeOffset))
{
convertColsIdx.Add(col.Ordinal);
}
Expand All @@ -86,11 +86,19 @@ public static DataTable ConvertUTCToAppTimeZone(ref DataTable dt, List<string> c
{
foreach (DataRow row in dt.Rows)
{
foreach (var col in convertColsIdx)
foreach (var colIdx in convertColsIdx)
{
if (row[col] != DBNull.Value)
if (row[colIdx] != DBNull.Value)
{
row[col] = ((DateTime)row[col]).ToAppTimeZone();
var colType = dt.Columns[colIdx].DataType;
if (colType == typeof(DateTime))
{
row[colIdx] = ((DateTime)row[colIdx]).ToAppTimeZone();
}
else if (colType == typeof(DateTimeOffset))
{
row[colIdx] = ((DateTimeOffset)row[colIdx]).ToAppTimeZone();
}
}
}
}
Expand Down Expand Up @@ -143,6 +151,11 @@ public static DateTime AppTimeZoneToUtc(this DateTime value)
return TimeZoneInfo.ConvertTimeToUtc(value, AppTimeZone);
}

public static DateTimeOffset ToAppTimeZone(this DateTimeOffset value)
{
return TimeZoneInfo.ConvertTime(value, AppTimeZone);
}

/// <summary>
/// Returns current DateTime in time zone of app
/// </summary>
Expand Down
39 changes: 31 additions & 8 deletions DBADashGUI/Performance/QueryStoreTopQueries.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit c9b9c45

Please sign in to comment.