mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-14 01:25:40 -05:00
Query Store Service (#2171)
* Checkpoint * Checkpoint * Checkpoint * checkpoint * Hooking in calls to QueryExecutionService * adding cases * Fleshing out report handlers * Adding parameter converters * Adding sqlparam declarations for Top Resource Consumers and Forced Plans * swapping to object-object to centralize conversion for sqlparams * Adding sqlparams for GetTrackedQueries * Added sqlparams for High Variation * Added Overall ResourceConumption * Adding params for regressed queries * Removing WithWaitStats calls, since they're automatically used within QSM when waitstats is an available statistic# * Adding PlanSummary handlers * cleaning up orderable queries * initial test mockout * adding basic (incorrect) parameter translation * first test passing, datetimeoffset swapped to ISO format * Adding test baselines * Updating nuget package * Adding get/set * Adding get/set for result object * Switching to parameter-less constructor * Swapping TimeInterval for string-based BasicTimeInterval * Removing unnecessary usings * Adding back params comments * Fixing up request docstrings * comment tweak * fix tests failing in pipeline because of line endings not matching * removing unnecessary usings * Setting tests to generate queries in UTC for test stability * Normalizing line endings --------- Co-authored-by: Kim Santiago <kisantia@microsoft.com>
This commit is contained in:
@@ -29,6 +29,7 @@
|
||||
<PackageReference Update="Microsoft.SqlServer.Assessment" Version="[1.1.17]" />
|
||||
<PackageReference Update="Microsoft.SqlServer.Migration.Assessment" Version="1.0.20230301.46" />
|
||||
<PackageReference Update="Microsoft.SqlServer.Migration.Logins" Version="1.0.20230407.56" />
|
||||
<PackageReference Update="Microsoft.SqlServer.Management.QueryStoreModel" Version="163.26.1" />
|
||||
<PackageReference Update="Microsoft.SqlServer.Management.SqlParser" Version="170.9.0" />
|
||||
<PackageReference Update="Microsoft.SqlServer.Migration.Tde" Version="1.0.0-preview.1.0.20230720.98" />
|
||||
<PackageReference Update="Microsoft.Azure.OperationalInsights" Version="1.0.0" />
|
||||
|
||||
Binary file not shown.
@@ -32,6 +32,7 @@ using Microsoft.SqlTools.ServiceLayer.NotebookConvert;
|
||||
using Microsoft.SqlTools.ServiceLayer.ObjectManagement;
|
||||
using Microsoft.SqlTools.ServiceLayer.Profiler;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryStore;
|
||||
using Microsoft.SqlTools.ServiceLayer.SchemaCompare;
|
||||
using Microsoft.SqlTools.ServiceLayer.Scripting;
|
||||
using Microsoft.SqlTools.ServiceLayer.ServerConfigurations;
|
||||
@@ -175,6 +176,9 @@ namespace Microsoft.SqlTools.ServiceLayer
|
||||
SqlProjectsService.Instance.InitializeService(serviceHost);
|
||||
serviceProvider.RegisterSingleService(SqlProjectsService.Instance);
|
||||
|
||||
QueryStoreService.Instance.InitializeService(serviceHost);
|
||||
serviceProvider.RegisterSingleService(QueryStoreService.Instance);
|
||||
|
||||
serviceHost.InitializeRequestHandlers();
|
||||
}
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
<PackageReference Include="Microsoft.Extensions.FileSystemGlobbing" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider" />
|
||||
<PackageReference Include="Microsoft.SqlServer.Management.QueryStoreModel" />
|
||||
<PackageReference Include="Microsoft.SqlServer.Management.SmoMetadataProvider" />
|
||||
<PackageReference Include="Microsoft.SqlServer.SqlManagementObjects" />
|
||||
<PackageReference Include="Microsoft.SqlServer.Management.SqlParser" />
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using Microsoft.SqlServer.Management.QueryStoreModel.Common;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.QueryStore
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a TimeInterval with strings for the start and end times instead of DateTimeOffsets for JRPC compatibility
|
||||
/// </summary>
|
||||
public class BasicTimeInterval
|
||||
{
|
||||
/// <summary>
|
||||
/// Start time of this time interval, in ISO 8601 format (<code>ToString("O")</code>).
|
||||
/// This property is ignored unless TimeIntervalOptions is set to Custom.
|
||||
/// </summary>
|
||||
public string StartDateTimeInUtc { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// End time of this time interval, in ISO 8601 format (<code>ToString("O")</code>).
|
||||
/// This property is ignored unless TimeIntervalOptions is set to Custom.
|
||||
/// </summary>
|
||||
public string EndDateTimeInUtc { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Time interval type. Unless set to Custom, then StartDateTimeInUtc and EndDateTimeInUtc are ignored.
|
||||
/// </summary>
|
||||
public TimeIntervalOptions TimeIntervalOptions { get; set; } = TimeIntervalOptions.Custom;
|
||||
|
||||
public TimeInterval Convert()
|
||||
{
|
||||
if (TimeIntervalOptions == TimeIntervalOptions.Custom
|
||||
&& !String.IsNullOrWhiteSpace(StartDateTimeInUtc)
|
||||
&& !String.IsNullOrWhiteSpace(EndDateTimeInUtc))
|
||||
{
|
||||
return new TimeInterval(DateTimeOffset.Parse(StartDateTimeInUtc), DateTimeOffset.Parse(EndDateTimeInUtc));
|
||||
}
|
||||
else if (TimeIntervalOptions != TimeIntervalOptions.Custom
|
||||
&& String.IsNullOrWhiteSpace(StartDateTimeInUtc)
|
||||
&& String.IsNullOrWhiteSpace(EndDateTimeInUtc))
|
||||
{
|
||||
return new TimeInterval(TimeIntervalOptions);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException($"{nameof(BasicTimeInterval)} was not populated correctly: '{TimeIntervalOptions}', '{StartDateTimeInUtc}' - '{EndDateTimeInUtc}'");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.SqlServer.Management.QueryStoreModel.ForcedPlanQueries;
|
||||
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.QueryStore.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// Parameters for getting a Forced Plan Queries report
|
||||
/// </summary>
|
||||
public class GetForcedPlanQueriesReportParams : OrderableQueryConfigurationParams<ForcedPlanQueriesConfiguration>
|
||||
{
|
||||
/// <summary>
|
||||
/// Time interval for the report
|
||||
/// </summary>
|
||||
public BasicTimeInterval TimeInterval { get; set; }
|
||||
|
||||
public override ForcedPlanQueriesConfiguration Convert()
|
||||
{
|
||||
ForcedPlanQueriesConfiguration config = base.Convert();
|
||||
config.TimeInterval = TimeInterval.Convert();
|
||||
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the query for a Forced Plan Queries report
|
||||
/// </summary>
|
||||
public class GetForcedPlanQueriesReportRequest
|
||||
{
|
||||
public static readonly RequestType<GetForcedPlanQueriesReportParams, QueryStoreQueryResult> Type
|
||||
= RequestType<GetForcedPlanQueriesReportParams, QueryStoreQueryResult>.Create("queryStore/getForcedPlanQueriesReport");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.SqlServer.Management.QueryStoreModel.HighVariation;
|
||||
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.QueryStore.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// Parameters for getting a High Variation Queries report
|
||||
/// </summary>
|
||||
public class GetHighVariationQueriesReportParams : OrderableQueryConfigurationParams<HighVariationConfiguration>
|
||||
{
|
||||
/// <summary>
|
||||
/// Time interval for the report
|
||||
/// </summary>
|
||||
public BasicTimeInterval TimeInterval { get; set; }
|
||||
|
||||
public override HighVariationConfiguration Convert()
|
||||
{
|
||||
HighVariationConfiguration config = base.Convert();
|
||||
config.TimeInterval = TimeInterval.Convert();
|
||||
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the query for a High Variation Queries report
|
||||
/// </summary>
|
||||
public class GetHighVariationQueriesSummaryRequest
|
||||
{
|
||||
public static readonly RequestType<GetHighVariationQueriesReportParams, QueryStoreQueryResult> Type
|
||||
= RequestType<GetHighVariationQueriesReportParams, QueryStoreQueryResult>.Create("queryStore/getHighVariationQueriesSummary");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the query for a detailed High Variation Queries report
|
||||
/// </summary>
|
||||
public class GetHighVariationQueriesDetailedSummaryRequest
|
||||
{
|
||||
public static readonly RequestType<GetHighVariationQueriesReportParams, QueryStoreQueryResult> Type
|
||||
= RequestType<GetHighVariationQueriesReportParams, QueryStoreQueryResult>.Create("queryStore/getHighVariationQueriesDetailedSummary");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.SqlServer.Management.QueryStoreModel.Common;
|
||||
using Microsoft.SqlServer.Management.QueryStoreModel.OverallResourceConsumption;
|
||||
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.QueryStore.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// Parameters for getting an Overall Resource Consumption report
|
||||
/// </summary>
|
||||
public class GetOverallResourceConsumptionReportParams : QueryConfigurationParams<OverallResourceConsumptionConfiguration>
|
||||
{
|
||||
/// <summary>
|
||||
/// Time interval for the report
|
||||
/// </summary>
|
||||
public BasicTimeInterval SpecifiedTimeInterval { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Bucket interval for the report
|
||||
/// </summary>
|
||||
public BucketInterval SpecifiedBucketInterval { get; set; }
|
||||
|
||||
public override OverallResourceConsumptionConfiguration Convert()
|
||||
{
|
||||
OverallResourceConsumptionConfiguration result = base.Convert();
|
||||
|
||||
result.SpecifiedTimeInterval = SpecifiedTimeInterval.Convert();
|
||||
result.SelectedBucketInterval = SpecifiedBucketInterval;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the query for an Overall Resource Consumption report
|
||||
/// </summary>
|
||||
public class GetOverallResourceConsumptionReportRequest
|
||||
{
|
||||
public static readonly RequestType<GetOverallResourceConsumptionReportParams, QueryStoreQueryResult> Type
|
||||
= RequestType<GetOverallResourceConsumptionReportParams, QueryStoreQueryResult>.Create("queryStore/getOverallResourceConsumptionReport");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.SqlServer.Management.QueryStoreModel.RegressedQueries;
|
||||
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.QueryStore.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// Parameters for getting a Regressed Queries report
|
||||
/// </summary>
|
||||
public class GetRegressedQueriesReportParams : QueryConfigurationParams<RegressedQueriesConfiguration>
|
||||
{
|
||||
/// <summary>
|
||||
/// Time interval during which to look for performance regressions for the report
|
||||
/// </summary>
|
||||
public BasicTimeInterval TimeIntervalRecent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Time interval during which to establish baseline performance for the report
|
||||
/// </summary>
|
||||
public BasicTimeInterval TimeIntervalHistory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Minimum number of executions for a query to be included
|
||||
/// </summary>
|
||||
public long MinExecutionCount { get; set; }
|
||||
|
||||
public override RegressedQueriesConfiguration Convert()
|
||||
{
|
||||
RegressedQueriesConfiguration result = base.Convert();
|
||||
|
||||
result.TimeIntervalRecent = TimeIntervalRecent.Convert();
|
||||
result.TimeIntervalHistory = TimeIntervalHistory.Convert();
|
||||
result.MinExecutionCount = MinExecutionCount;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the query for a Regressed Queries report
|
||||
/// </summary>
|
||||
public class GetRegressedQueriesSummaryRequest
|
||||
{
|
||||
public static readonly RequestType<GetRegressedQueriesReportParams, QueryStoreQueryResult> Type
|
||||
= RequestType<GetRegressedQueriesReportParams, QueryStoreQueryResult>.Create("queryStore/getRegressedQueriesSummary");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the query for a detailed Regressed Queries report
|
||||
/// </summary>
|
||||
public class GetRegressedQueriesDetailedSummaryRequest
|
||||
{
|
||||
public static readonly RequestType<GetRegressedQueriesReportParams, QueryStoreQueryResult> Type
|
||||
= RequestType<GetRegressedQueriesReportParams, QueryStoreQueryResult>.Create("queryStore/getRegressedQueriesDetailedSummary");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.SqlServer.Management.QueryStoreModel.TopResourceConsumers;
|
||||
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.QueryStore.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// Parameters for getting a Top Resource Consumers report
|
||||
/// </summary>
|
||||
public class GetTopResourceConsumersReportParams : OrderableQueryConfigurationParams<TopResourceConsumersConfiguration>
|
||||
{
|
||||
/// <summary>
|
||||
/// Time interval for the report
|
||||
/// </summary>
|
||||
public BasicTimeInterval TimeInterval { get; set; }
|
||||
|
||||
public override TopResourceConsumersConfiguration Convert()
|
||||
{
|
||||
TopResourceConsumersConfiguration result = base.Convert();
|
||||
result.TimeInterval = TimeInterval.Convert();
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the query for a Top Resource Consumers report
|
||||
/// </summary>
|
||||
public class GetTopResourceConsumersSummaryRequest
|
||||
{
|
||||
public static readonly RequestType<GetTopResourceConsumersReportParams, QueryStoreQueryResult> Type
|
||||
= RequestType<GetTopResourceConsumersReportParams, QueryStoreQueryResult>.Create("queryStore/getTopResourceConsumersSummary");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the query for a detailed Top Resource Consumers report
|
||||
/// </summary>
|
||||
public class GetTopResourceConsumersDetailedSummaryRequest
|
||||
{
|
||||
public static readonly RequestType<GetTopResourceConsumersReportParams, QueryStoreQueryResult> Type
|
||||
= RequestType<GetTopResourceConsumersReportParams, QueryStoreQueryResult>.Create("queryStore/getTopResourceConsumersDetailedSummary");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.QueryStore.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// Parameters for getting a Tracked Queries report
|
||||
/// </summary>
|
||||
public class GetTrackedQueriesReportParams
|
||||
{
|
||||
/// <summary>
|
||||
/// Search text for a query
|
||||
/// </summary>
|
||||
public string QuerySearchText { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the query for a Tracked Queries report
|
||||
/// </summary>
|
||||
public class GetTrackedQueriesReportRequest
|
||||
{
|
||||
public static readonly RequestType<GetTrackedQueriesReportParams, QueryStoreQueryResult> Type
|
||||
= RequestType<GetTrackedQueriesReportParams, QueryStoreQueryResult>.Create("queryStore/getTrackedQueriesReport");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
#nullable disable
|
||||
|
||||
using Microsoft.SqlServer.Management.QueryStoreModel.Common;
|
||||
using Microsoft.SqlServer.Management.QueryStoreModel.PlanSummary;
|
||||
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
|
||||
using static Microsoft.SqlServer.Management.QueryStoreModel.PlanSummary.PlanSummaryConfiguration;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.QueryStore.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// Parameters for getting a Plan Summary
|
||||
/// </summary>
|
||||
public class GetPlanSummaryParams : TypedQueryStoreReportParams<PlanSummaryConfiguration>
|
||||
{
|
||||
/// <summary>
|
||||
/// Query ID to view a summary of plans for
|
||||
/// </summary>
|
||||
public long QueryId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Mode of the time interval search
|
||||
/// </summary>
|
||||
public PlanTimeIntervalMode TimeIntervalMode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Time interval for the report
|
||||
/// </summary>
|
||||
public BasicTimeInterval TimeInterval { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Metric to summarize
|
||||
/// </summary>
|
||||
public Metric SelectedMetric { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Statistic to calculate on SelecticMetric
|
||||
/// </summary>
|
||||
public Statistic SelectedStatistic { get; set; }
|
||||
|
||||
public override PlanSummaryConfiguration Convert() => new()
|
||||
{
|
||||
QueryId = QueryId,
|
||||
TimeIntervalMode = TimeIntervalMode,
|
||||
TimeInterval = TimeInterval.Convert(),
|
||||
SelectedMetric = SelectedMetric,
|
||||
SelectedStatistic = SelectedStatistic
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parameters for getting the grid view of a Plan Summary
|
||||
/// </summary>
|
||||
public class GetPlanSummaryGridViewParams : GetPlanSummaryParams, IOrderableQueryParams
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of the column to order results by
|
||||
/// </summary>
|
||||
public string OrderByColumnId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Direction of the result ordering
|
||||
/// </summary>
|
||||
public bool Descending { get; set; }
|
||||
|
||||
public string GetOrderByColumnId() => OrderByColumnId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parameters for getting the forced plan for a query
|
||||
/// </summary>
|
||||
public class GetForcedPlanParams : QueryStoreReportParams
|
||||
{
|
||||
/// <summary>
|
||||
/// Query ID to view the plan for
|
||||
/// </summary>
|
||||
public long QueryId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Plan ID to view
|
||||
/// </summary>
|
||||
public long PlanId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the query for a Plan Summary chart view
|
||||
/// </summary>
|
||||
public class GetPlanSummaryChartViewRequest
|
||||
{
|
||||
public static readonly RequestType<GetPlanSummaryParams, QueryStoreQueryResult> Type
|
||||
= RequestType<GetPlanSummaryParams, QueryStoreQueryResult>.Create("queryStore/getPlanSummaryChartView");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the query for a Plan Summary grid view
|
||||
/// </summary>
|
||||
public class GetPlanSummaryGridViewRequest
|
||||
{
|
||||
public static readonly RequestType<GetPlanSummaryGridViewParams, QueryStoreQueryResult> Type
|
||||
= RequestType<GetPlanSummaryGridViewParams, QueryStoreQueryResult>.Create("queryStore/getPlanSummaryGridView");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the query to view a forced plan
|
||||
/// </summary>
|
||||
public class GetForcedPlanRequest // there's also GetForcedPlanQueries (plural) in QSM; how is that not confusing...
|
||||
{
|
||||
public static readonly RequestType<GetForcedPlanParams, QueryStoreQueryResult> Type
|
||||
= RequestType<GetForcedPlanParams, QueryStoreQueryResult>.Create("queryStore/getForcedPlan");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
#nullable disable
|
||||
|
||||
using Microsoft.SqlServer.Management.QueryStoreModel.Common;
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.QueryStore.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for a Query Store report parameters
|
||||
/// </summary>
|
||||
public abstract class QueryStoreReportParams
|
||||
{
|
||||
/// <summary>
|
||||
/// Connection URI for the database
|
||||
/// </summary>
|
||||
public string ConnectionOwnerUri { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base class for Query Store report parameters that can be converted to a configuration object for use in QSM query generators
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public abstract class TypedQueryStoreReportParams<T> : QueryStoreReportParams
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts this SQL Tools Service parameter object to the QSM configuration object
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public abstract T Convert();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base class for parameters for a report type that uses QueryConfigurationBase for its configuration
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public abstract class QueryConfigurationParams<T> : TypedQueryStoreReportParams<T> where T : QueryConfigurationBase, new()
|
||||
{
|
||||
/// <summary>
|
||||
/// Metric to summarize
|
||||
/// </summary>
|
||||
public Metric SelectedMetric { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Statistic to calculate on SelecticMetric
|
||||
/// </summary>
|
||||
public Statistic SelectedStatistic { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of queries to return if ReturnAllQueries is not set
|
||||
/// </summary>
|
||||
public int TopQueriesReturned { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// True to include all queries in the report; false to only include the top queries, up to the value specified by TopQueriesReturned
|
||||
/// </summary>
|
||||
public bool ReturnAllQueries { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Minimum number of query plans for a query to included in the report
|
||||
/// </summary>
|
||||
public int MinNumberOfQueryPlans { get; set; }
|
||||
|
||||
public override T Convert() => new T()
|
||||
{
|
||||
SelectedMetric = SelectedMetric,
|
||||
SelectedStatistic = SelectedStatistic,
|
||||
TopQueriesReturned = TopQueriesReturned,
|
||||
ReturnAllQueries = ReturnAllQueries,
|
||||
MinNumberOfQueryPlans = MinNumberOfQueryPlans
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base class for parameters for a report that can be ordered by a specified column
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public abstract class OrderableQueryConfigurationParams<T> : QueryConfigurationParams<T>, IOrderableQueryParams where T : QueryConfigurationBase, new()
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of the column to order results by
|
||||
/// </summary>
|
||||
public string OrderByColumnId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Direction of the result ordering
|
||||
/// </summary>
|
||||
public bool Descending { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the column to order the report results by
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string GetOrderByColumnId() => OrderByColumnId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result containing a finalized query for a report
|
||||
/// </summary>
|
||||
public class QueryStoreQueryResult : ResultStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// Finalized query for a report
|
||||
/// </summary>
|
||||
public string Query { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface for parameters for a report that can be ordered by a specific column
|
||||
/// </summary>
|
||||
public interface IOrderableQueryParams
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the name of the column to order the report results by
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
string GetOrderByColumnId();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,552 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.SqlServer.Management.QueryStoreModel.Common;
|
||||
using Microsoft.SqlServer.Management.QueryStoreModel.ForcedPlanQueries;
|
||||
using Microsoft.SqlServer.Management.QueryStoreModel.HighVariation;
|
||||
using Microsoft.SqlServer.Management.QueryStoreModel.OverallResourceConsumption;
|
||||
using Microsoft.SqlServer.Management.QueryStoreModel.PlanSummary;
|
||||
using Microsoft.SqlServer.Management.QueryStoreModel.RegressedQueries;
|
||||
using Microsoft.SqlServer.Management.QueryStoreModel.TopResourceConsumers;
|
||||
using Microsoft.SqlServer.Management.QueryStoreModel.TrackedQueries;
|
||||
using Microsoft.SqlTools.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryStore.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.QueryStore
|
||||
{
|
||||
/// <summary>
|
||||
/// Main class for Query Store service
|
||||
/// </summary>
|
||||
public class QueryStoreService : BaseService
|
||||
{
|
||||
private static readonly Lazy<QueryStoreService> instance = new Lazy<QueryStoreService>(() => new QueryStoreService());
|
||||
|
||||
/// <summary>
|
||||
/// Gets the singleton instance object
|
||||
/// </summary>
|
||||
public static QueryStoreService Instance => instance.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Instance of the connection service, used to get the connection info for a given owner URI
|
||||
/// </summary>
|
||||
private ConnectionService ConnectionService { get; }
|
||||
|
||||
public QueryStoreService()
|
||||
{
|
||||
ConnectionService = ConnectionService.Instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the service instance
|
||||
/// </summary>
|
||||
/// <param name="serviceHost"></param>
|
||||
public void InitializeService(ServiceHost serviceHost)
|
||||
{
|
||||
// Top Resource Consumers report
|
||||
serviceHost.SetRequestHandler(GetTopResourceConsumersSummaryRequest.Type, HandleGetTopResourceConsumersSummaryReportRequest, isParallelProcessingSupported: true);
|
||||
serviceHost.SetRequestHandler(GetTopResourceConsumersDetailedSummaryRequest.Type, HandleGetTopResourceConsumersDetailedSummaryReportRequest, isParallelProcessingSupported: true);
|
||||
|
||||
// Forced Plan Queries report
|
||||
serviceHost.SetRequestHandler(GetForcedPlanQueriesReportRequest.Type, HandleGetForcedPlanQueriesReportRequest, isParallelProcessingSupported: true);
|
||||
|
||||
// Tracked Queries report
|
||||
serviceHost.SetRequestHandler(GetTrackedQueriesReportRequest.Type, HandleGetTrackedQueriesReportRequest, isParallelProcessingSupported: true);
|
||||
|
||||
// High Variation Queries report
|
||||
serviceHost.SetRequestHandler(GetHighVariationQueriesSummaryRequest.Type, HandleGetHighVariationQueriesSummaryReportRequest, isParallelProcessingSupported: true);
|
||||
serviceHost.SetRequestHandler(GetHighVariationQueriesDetailedSummaryRequest.Type, HandleGetHighVariationQueriesDetailedSummaryReportRequest, isParallelProcessingSupported: true);
|
||||
|
||||
// Overall Resource Consumption report
|
||||
serviceHost.SetRequestHandler(GetOverallResourceConsumptionReportRequest.Type, HandleGetOverallResourceConsumptionReportRequest, isParallelProcessingSupported: true);
|
||||
|
||||
// Regressed Queries report
|
||||
serviceHost.SetRequestHandler(GetRegressedQueriesSummaryRequest.Type, HandleGetRegressedQueriesSummaryReportRequest, isParallelProcessingSupported: true);
|
||||
serviceHost.SetRequestHandler(GetRegressedQueriesDetailedSummaryRequest.Type, HandleGetRegressedQueriesDetailedSummaryReportRequest, isParallelProcessingSupported: true);
|
||||
|
||||
// Plan Summary report
|
||||
serviceHost.SetRequestHandler(GetPlanSummaryChartViewRequest.Type, HandleGetPlanSummaryChartViewRequest, isParallelProcessingSupported: true);
|
||||
serviceHost.SetRequestHandler(GetPlanSummaryGridViewRequest.Type, HandleGetPlanSummaryGridViewRequest, isParallelProcessingSupported: true);
|
||||
serviceHost.SetRequestHandler(GetForcedPlanRequest.Type, HandleGetForcedPlanRequest, isParallelProcessingSupported: true);
|
||||
}
|
||||
|
||||
#region Handlers
|
||||
|
||||
/*
|
||||
* General process is to:
|
||||
* 1. Convert the ADS config to the QueryStoreModel config format
|
||||
* 2. Call the unordered query generator to get the list of columns
|
||||
* 3. Select the intended ColumnInfo for sorting
|
||||
* 4. Call the ordered query generator to get the actual query
|
||||
* 5. Prepend any necessary TSQL parameters to the generated query
|
||||
* 6. Return the query text to ADS for execution
|
||||
*/
|
||||
|
||||
#region Top Resource Consumers report
|
||||
|
||||
internal async Task HandleGetTopResourceConsumersSummaryReportRequest(GetTopResourceConsumersReportParams requestParams, RequestContext<QueryStoreQueryResult> requestContext)
|
||||
{
|
||||
await RunWithErrorHandling(() =>
|
||||
{
|
||||
TopResourceConsumersConfiguration config = requestParams.Convert();
|
||||
TopResourceConsumersQueryGenerator.TopResourceConsumersSummary(config, out IList<ColumnInfo> columns);
|
||||
ColumnInfo orderByColumn = GetOrderByColumn(requestParams, columns);
|
||||
|
||||
string query = TopResourceConsumersQueryGenerator.TopResourceConsumersSummary(config, orderByColumn, requestParams.Descending, out _);
|
||||
|
||||
Dictionary<string, object> sqlParams = new()
|
||||
{
|
||||
[QueryGeneratorUtils.ParameterIntervalStartTime] = config.TimeInterval.StartDateTimeOffset,
|
||||
[QueryGeneratorUtils.ParameterIntervalEndTime] = config.TimeInterval.EndDateTimeOffset
|
||||
};
|
||||
|
||||
if (!config.ReturnAllQueries)
|
||||
{
|
||||
sqlParams[QueryGeneratorUtils.ParameterResultsRowCount] = config.TopQueriesReturned;
|
||||
}
|
||||
|
||||
query = PrependSqlParameters(query, sqlParams);
|
||||
|
||||
return new QueryStoreQueryResult()
|
||||
{
|
||||
Success = true,
|
||||
ErrorMessage = null,
|
||||
Query = query,
|
||||
};
|
||||
}, requestContext);
|
||||
}
|
||||
|
||||
internal async Task HandleGetTopResourceConsumersDetailedSummaryReportRequest(GetTopResourceConsumersReportParams requestParams, RequestContext<QueryStoreQueryResult> requestContext)
|
||||
{
|
||||
await RunWithErrorHandling(() =>
|
||||
{
|
||||
TopResourceConsumersConfiguration config = requestParams.Convert();
|
||||
TopResourceConsumersQueryGenerator.TopResourceConsumersDetailedSummary(GetAvailableMetrics(requestParams), config, out IList<ColumnInfo> columns);
|
||||
ColumnInfo orderByColumn = GetOrderByColumn(requestParams, columns);
|
||||
|
||||
string query = TopResourceConsumersQueryGenerator.TopResourceConsumersDetailedSummary(GetAvailableMetrics(requestParams), config, orderByColumn, requestParams.Descending, out _);
|
||||
|
||||
Dictionary<string, object> sqlParams = new()
|
||||
{
|
||||
[QueryGeneratorUtils.ParameterIntervalStartTime] = config.TimeInterval.StartDateTimeOffset,
|
||||
[QueryGeneratorUtils.ParameterIntervalEndTime] = config.TimeInterval.EndDateTimeOffset
|
||||
};
|
||||
|
||||
if (!config.ReturnAllQueries)
|
||||
{
|
||||
sqlParams[QueryGeneratorUtils.ParameterResultsRowCount] = config.TopQueriesReturned;
|
||||
}
|
||||
|
||||
query = PrependSqlParameters(query, sqlParams);
|
||||
|
||||
return new QueryStoreQueryResult()
|
||||
{
|
||||
Success = true,
|
||||
ErrorMessage = null,
|
||||
Query = query
|
||||
};
|
||||
}, requestContext);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Forced Plans report
|
||||
|
||||
internal async Task HandleGetForcedPlanQueriesReportRequest(GetForcedPlanQueriesReportParams requestParams, RequestContext<QueryStoreQueryResult> requestContext)
|
||||
{
|
||||
await RunWithErrorHandling(() =>
|
||||
{
|
||||
ForcedPlanQueriesConfiguration config = requestParams.Convert();
|
||||
ForcedPlanQueriesQueryGenerator.ForcedPlanQueriesSummary(config, out IList<ColumnInfo> columns);
|
||||
ColumnInfo orderByColumn = GetOrderByColumn(requestParams, columns);
|
||||
|
||||
string query = ForcedPlanQueriesQueryGenerator.ForcedPlanQueriesSummary(config, orderByColumn, requestParams.Descending, out IList<ColumnInfo> _);
|
||||
|
||||
if (!config.ReturnAllQueries)
|
||||
{
|
||||
query = PrependSqlParameters(query, new() { [QueryGeneratorUtils.ParameterResultsRowCount] = config.TopQueriesReturned.ToString() });
|
||||
}
|
||||
|
||||
return new QueryStoreQueryResult()
|
||||
{
|
||||
Success = true,
|
||||
ErrorMessage = null,
|
||||
Query = query
|
||||
};
|
||||
}, requestContext);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Tracked Queries report
|
||||
|
||||
internal async Task HandleGetTrackedQueriesReportRequest(GetTrackedQueriesReportParams requestParams, RequestContext<QueryStoreQueryResult> requestContext)
|
||||
{
|
||||
await RunWithErrorHandling(() =>
|
||||
{
|
||||
string query = QueryIDSearchQueryGenerator.GetQuery();
|
||||
|
||||
query = PrependSqlParameters(query, new() { [QueryIDSearchQueryGenerator.QuerySearchTextParameter] = requestParams.QuerySearchText });
|
||||
|
||||
return new QueryStoreQueryResult()
|
||||
{
|
||||
Success = true,
|
||||
ErrorMessage = null,
|
||||
Query = query
|
||||
};
|
||||
}, requestContext);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region High Variation Queries report
|
||||
|
||||
internal async Task HandleGetHighVariationQueriesSummaryReportRequest(GetHighVariationQueriesReportParams requestParams, RequestContext<QueryStoreQueryResult> requestContext)
|
||||
{
|
||||
await RunWithErrorHandling(() =>
|
||||
{
|
||||
HighVariationConfiguration config = requestParams.Convert();
|
||||
HighVariationQueryGenerator.HighVariationSummary(config, out IList<ColumnInfo> columns);
|
||||
ColumnInfo orderByColumn = GetOrderByColumn(requestParams, columns);
|
||||
|
||||
string query = HighVariationQueryGenerator.HighVariationSummary(config, orderByColumn, requestParams.Descending, out _);
|
||||
|
||||
Dictionary<string, object> sqlParams = new()
|
||||
{
|
||||
[QueryGeneratorUtils.ParameterIntervalStartTime] = config.TimeInterval.StartDateTimeOffset,
|
||||
[QueryGeneratorUtils.ParameterIntervalEndTime] = config.TimeInterval.EndDateTimeOffset
|
||||
};
|
||||
|
||||
if (!config.ReturnAllQueries)
|
||||
{
|
||||
sqlParams[QueryGeneratorUtils.ParameterResultsRowCount] = config.TopQueriesReturned;
|
||||
}
|
||||
|
||||
query = PrependSqlParameters(query, sqlParams);
|
||||
|
||||
return new QueryStoreQueryResult()
|
||||
{
|
||||
Success = true,
|
||||
ErrorMessage = null,
|
||||
Query = query
|
||||
};
|
||||
}, requestContext);
|
||||
}
|
||||
|
||||
internal async Task HandleGetHighVariationQueriesDetailedSummaryReportRequest(GetHighVariationQueriesReportParams requestParams, RequestContext<QueryStoreQueryResult> requestContext)
|
||||
{
|
||||
await RunWithErrorHandling(() =>
|
||||
{
|
||||
HighVariationConfiguration config = requestParams.Convert();
|
||||
IList<Metric> availableMetrics = GetAvailableMetrics(requestParams);
|
||||
HighVariationQueryGenerator.HighVariationDetailedSummary(availableMetrics, config, out IList<ColumnInfo> columns);
|
||||
ColumnInfo orderByColumn = GetOrderByColumn(requestParams, columns);
|
||||
|
||||
string query = HighVariationQueryGenerator.HighVariationDetailedSummary(availableMetrics, config, orderByColumn, requestParams.Descending, out _);
|
||||
|
||||
Dictionary<string, object> sqlParams = new()
|
||||
{
|
||||
[QueryGeneratorUtils.ParameterIntervalStartTime] = config.TimeInterval.StartDateTimeOffset,
|
||||
[QueryGeneratorUtils.ParameterIntervalEndTime] = config.TimeInterval.EndDateTimeOffset
|
||||
};
|
||||
|
||||
if (!config.ReturnAllQueries)
|
||||
{
|
||||
sqlParams[QueryGeneratorUtils.ParameterResultsRowCount] = config.TopQueriesReturned;
|
||||
}
|
||||
|
||||
query = PrependSqlParameters(query, sqlParams);
|
||||
|
||||
return new QueryStoreQueryResult()
|
||||
{
|
||||
Success = true,
|
||||
ErrorMessage = null,
|
||||
Query = query
|
||||
};
|
||||
}, requestContext);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Overall Resource Consumption report
|
||||
|
||||
internal async Task HandleGetOverallResourceConsumptionReportRequest(GetOverallResourceConsumptionReportParams requestParams, RequestContext<QueryStoreQueryResult> requestContext)
|
||||
{
|
||||
await RunWithErrorHandling(() =>
|
||||
{
|
||||
OverallResourceConsumptionConfiguration config = requestParams.Convert();
|
||||
string query = OverallResourceConsumptionQueryGenerator.GenerateQuery(GetAvailableMetrics(requestParams), config, out _);
|
||||
|
||||
Dictionary<string, object> sqlParams = new()
|
||||
{
|
||||
[QueryGeneratorUtils.ParameterIntervalStartTime] = config.SpecifiedTimeInterval.StartDateTimeOffset,
|
||||
[QueryGeneratorUtils.ParameterIntervalEndTime] = config.SpecifiedTimeInterval.EndDateTimeOffset
|
||||
};
|
||||
|
||||
query = PrependSqlParameters(query, sqlParams);
|
||||
|
||||
return new QueryStoreQueryResult()
|
||||
{
|
||||
Success = true,
|
||||
ErrorMessage = null,
|
||||
Query = query
|
||||
};
|
||||
}, requestContext);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Regressed Queries report
|
||||
|
||||
internal async Task HandleGetRegressedQueriesSummaryReportRequest(GetRegressedQueriesReportParams requestParams, RequestContext<QueryStoreQueryResult> requestContext)
|
||||
{
|
||||
await RunWithErrorHandling(() =>
|
||||
{
|
||||
RegressedQueriesConfiguration config = requestParams.Convert();
|
||||
string query = RegressedQueriesQueryGenerator.RegressedQuerySummary(config, out _);
|
||||
|
||||
Dictionary<string, object> sqlParams = new()
|
||||
{
|
||||
[RegressedQueriesQueryGenerator.ParameterRecentStartTime] = config.TimeIntervalRecent.StartDateTimeOffset,
|
||||
[RegressedQueriesQueryGenerator.ParameterRecentEndTime] = config.TimeIntervalRecent.EndDateTimeOffset,
|
||||
[RegressedQueriesQueryGenerator.ParameterHistoryStartTime] = config.TimeIntervalHistory.StartDateTimeOffset,
|
||||
[RegressedQueriesQueryGenerator.ParameterHistoryEndTime] = config.TimeIntervalHistory.EndDateTimeOffset,
|
||||
[RegressedQueriesQueryGenerator.ParameterMinExecutionCount] = config.MinExecutionCount
|
||||
};
|
||||
|
||||
if (!config.ReturnAllQueries)
|
||||
{
|
||||
sqlParams[QueryGeneratorUtils.ParameterResultsRowCount] = config.TopQueriesReturned;
|
||||
}
|
||||
|
||||
query = PrependSqlParameters(query, sqlParams);
|
||||
|
||||
|
||||
return new QueryStoreQueryResult()
|
||||
{
|
||||
Success = true,
|
||||
ErrorMessage = null,
|
||||
Query = query
|
||||
};
|
||||
}, requestContext);
|
||||
}
|
||||
|
||||
internal async Task HandleGetRegressedQueriesDetailedSummaryReportRequest(GetRegressedQueriesReportParams requestParams, RequestContext<QueryStoreQueryResult> requestContext)
|
||||
{
|
||||
await RunWithErrorHandling(() =>
|
||||
{
|
||||
RegressedQueriesConfiguration config = requestParams.Convert();
|
||||
string query = RegressedQueriesQueryGenerator.RegressedQueryDetailedSummary(GetAvailableMetrics(requestParams), config, out _);
|
||||
|
||||
Dictionary<string, object> sqlParams = new()
|
||||
{
|
||||
[RegressedQueriesQueryGenerator.ParameterRecentStartTime] = config.TimeIntervalRecent.StartDateTimeOffset,
|
||||
[RegressedQueriesQueryGenerator.ParameterRecentEndTime] = config.TimeIntervalRecent.EndDateTimeOffset,
|
||||
[RegressedQueriesQueryGenerator.ParameterHistoryStartTime] = config.TimeIntervalHistory.StartDateTimeOffset,
|
||||
[RegressedQueriesQueryGenerator.ParameterHistoryEndTime] = config.TimeIntervalHistory.EndDateTimeOffset,
|
||||
[RegressedQueriesQueryGenerator.ParameterMinExecutionCount] = config.MinExecutionCount
|
||||
};
|
||||
|
||||
if (!config.ReturnAllQueries)
|
||||
{
|
||||
sqlParams[QueryGeneratorUtils.ParameterResultsRowCount] = config.TopQueriesReturned;
|
||||
}
|
||||
|
||||
query = PrependSqlParameters(query, sqlParams);
|
||||
|
||||
return new QueryStoreQueryResult()
|
||||
{
|
||||
Success = true,
|
||||
ErrorMessage = null,
|
||||
Query = query
|
||||
};
|
||||
}, requestContext);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Plan Summary report
|
||||
|
||||
internal async Task HandleGetPlanSummaryChartViewRequest(GetPlanSummaryParams requestParams, RequestContext<QueryStoreQueryResult> requestContext)
|
||||
{
|
||||
await RunWithErrorHandling(() =>
|
||||
{
|
||||
PlanSummaryConfiguration config = requestParams.Convert();
|
||||
|
||||
BucketInterval bucketInterval = BucketInterval.Hour;
|
||||
|
||||
// if interval is specified then select a 'good' interval
|
||||
if (config.UseTimeInterval)
|
||||
{
|
||||
TimeSpan duration = config.TimeInterval.TimeSpan;
|
||||
bucketInterval = BucketIntervalUtils.CalculateGoodSubInterval(duration);
|
||||
}
|
||||
|
||||
string query = PlanSummaryQueryGenerator.PlanSummaryChartView(config, bucketInterval, out _);
|
||||
|
||||
Dictionary<string, object> sqlParams = new()
|
||||
{
|
||||
[QueryGeneratorUtils.ParameterQueryId] = config.QueryId
|
||||
};
|
||||
|
||||
if (config.UseTimeInterval)
|
||||
{
|
||||
sqlParams[QueryGeneratorUtils.ParameterIntervalStartTime] = config.TimeInterval.StartDateTimeOffset;
|
||||
sqlParams[QueryGeneratorUtils.ParameterIntervalEndTime] = config.TimeInterval.EndDateTimeOffset;
|
||||
}
|
||||
|
||||
query = PrependSqlParameters(query, sqlParams);
|
||||
|
||||
return new QueryStoreQueryResult()
|
||||
{
|
||||
Success = true,
|
||||
ErrorMessage = null,
|
||||
Query = query
|
||||
};
|
||||
}, requestContext);
|
||||
}
|
||||
|
||||
internal async Task HandleGetPlanSummaryGridViewRequest(GetPlanSummaryGridViewParams requestParams, RequestContext<QueryStoreQueryResult> requestContext)
|
||||
{
|
||||
await RunWithErrorHandling(() =>
|
||||
{
|
||||
PlanSummaryConfiguration config = requestParams.Convert();
|
||||
|
||||
PlanSummaryQueryGenerator.PlanSummaryGridView(config, out IList<ColumnInfo> columns);
|
||||
ColumnInfo orderByColumn = GetOrderByColumn(requestParams, columns);
|
||||
|
||||
string query = PlanSummaryQueryGenerator.PlanSummaryGridView(config, orderByColumn, requestParams.Descending, out _);
|
||||
|
||||
Dictionary<string, object> sqlParams = new()
|
||||
{
|
||||
[QueryGeneratorUtils.ParameterQueryId] = config.QueryId
|
||||
};
|
||||
|
||||
if (config.UseTimeInterval)
|
||||
{
|
||||
sqlParams[QueryGeneratorUtils.ParameterIntervalStartTime] = config.TimeInterval.StartDateTimeOffset;
|
||||
sqlParams[QueryGeneratorUtils.ParameterIntervalEndTime] = config.TimeInterval.EndDateTimeOffset;
|
||||
}
|
||||
|
||||
query = PrependSqlParameters(query, sqlParams);
|
||||
|
||||
return new QueryStoreQueryResult()
|
||||
{
|
||||
Success = true,
|
||||
ErrorMessage = null,
|
||||
Query = query
|
||||
};
|
||||
}, requestContext);
|
||||
}
|
||||
|
||||
internal async Task HandleGetForcedPlanRequest(GetForcedPlanParams requestParams, RequestContext<QueryStoreQueryResult> requestContext)
|
||||
{
|
||||
await RunWithErrorHandling(() =>
|
||||
{
|
||||
string query = PlanSummaryQueryGenerator.GetForcedPlanQuery();
|
||||
Dictionary<string, object> sqlParams = new()
|
||||
{
|
||||
[QueryGeneratorUtils.ParameterQueryId] = requestParams.QueryId,
|
||||
[QueryGeneratorUtils.ParameterPlanId] = requestParams.PlanId,
|
||||
};
|
||||
|
||||
query = PrependSqlParameters(query, sqlParams);
|
||||
|
||||
return new QueryStoreQueryResult()
|
||||
{
|
||||
Success = true,
|
||||
ErrorMessage = null,
|
||||
Query = query
|
||||
};
|
||||
}, requestContext);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helpers
|
||||
|
||||
private ColumnInfo GetOrderByColumn(IOrderableQueryParams requestParams, IList<ColumnInfo> columnInfoList)
|
||||
{
|
||||
return requestParams.GetOrderByColumnId() != null ? columnInfoList.First(col => col.GetQueryColumnLabel() == requestParams.GetOrderByColumnId()) : columnInfoList[0];
|
||||
}
|
||||
|
||||
internal virtual IList<Metric> GetAvailableMetrics(QueryStoreReportParams requestParams)
|
||||
{
|
||||
ConnectionService.TryFindConnection(requestParams.ConnectionOwnerUri, out ConnectionInfo connectionInfo);
|
||||
|
||||
if (connectionInfo != null)
|
||||
{
|
||||
using (SqlConnection connection = ConnectionService.OpenSqlConnection(connectionInfo, "QueryStoreService available metrics"))
|
||||
{
|
||||
return QdsMetadataMapper.GetAvailableMetrics(connection);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException($"Unable to find connection for '{requestParams.ConnectionOwnerUri}'");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prepends declarations and definitions of <paramref name="sqlParams"/> to <paramref name="query"/>
|
||||
/// </summary>
|
||||
/// <param name="query"></param>
|
||||
/// <param name="sqlParams"></param>
|
||||
/// <returns></returns>
|
||||
private static string PrependSqlParameters(string query, Dictionary<string, object> sqlParams)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
foreach (string key in sqlParams.Keys)
|
||||
{
|
||||
sb.AppendLine($"DECLARE {key} {GetTSqlRepresentation(sqlParams[key])};");
|
||||
}
|
||||
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(query);
|
||||
|
||||
return sb.ToString().Trim();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an object (that would otherwise be set as a SqlParameter value) to an entirely TSQL representation.
|
||||
/// Only handles the same subset of object types that Query Store query generators use:
|
||||
/// int, long, string, and DateTimeOffset
|
||||
/// </summary>
|
||||
/// <param name="paramValue"></param>
|
||||
/// <returns>data type and value portions of a parameter declaration, in the form "INT = 999"</returns>
|
||||
internal static string GetTSqlRepresentation(object paramValue)
|
||||
{
|
||||
switch (paramValue)
|
||||
{
|
||||
case int i:
|
||||
return $"INT = {i}";
|
||||
case long l:
|
||||
return $"BIGINT = {l}";
|
||||
case string s:
|
||||
return $"NVARCHAR(max) = N'{s.Replace("'", "''")}'";
|
||||
case DateTimeOffset dto:
|
||||
return $"DATETIMEOFFSET = '{dto.ToString("O", CultureInfo.InvariantCulture)}'"; // "O" = ISO 8601 standard datetime format
|
||||
default:
|
||||
Debug.Fail($"Unhandled TSQL parameter type: '{paramValue.GetType()}'");
|
||||
return $"= {paramValue}";
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,849 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.QueryStore
|
||||
{
|
||||
internal static class QueryStoreBaselines
|
||||
{
|
||||
public const string HandleGetTopResourceConsumersSummaryReportRequest =
|
||||
@"DECLARE @interval_start_time DATETIMEOFFSET = '2023-06-10T12:34:56.0000000+00:00';
|
||||
DECLARE @interval_end_time DATETIMEOFFSET = '2023-06-17T12:34:56.0000000+00:00';
|
||||
|
||||
With wait_stats AS
|
||||
(
|
||||
SELECT
|
||||
ws.plan_id plan_id,
|
||||
ws.wait_category,
|
||||
ROUND(CONVERT(float, SUM(ws.total_query_wait_time_ms)/SUM(ws.total_query_wait_time_ms/ws.avg_query_wait_time_ms))*1,2) avg_query_wait_time,
|
||||
ROUND(CONVERT(float, MIN(ws.min_query_wait_time_ms))*1,2) min_query_wait_time,
|
||||
ROUND(CONVERT(float, MAX(ws.max_query_wait_time_ms))*1,2) max_query_wait_time,
|
||||
ROUND(CONVERT(float, SQRT( SUM(ws.stdev_query_wait_time_ms*ws.stdev_query_wait_time_ms*(ws.total_query_wait_time_ms/ws.avg_query_wait_time_ms))/SUM(ws.total_query_wait_time_ms/ws.avg_query_wait_time_ms)))*1,2) stdev_query_wait_time,
|
||||
ROUND(CONVERT(float, SUM(ws.total_query_wait_time_ms))*1,2) total_query_wait_time,
|
||||
CAST(ROUND(SUM(ws.total_query_wait_time_ms/ws.avg_query_wait_time_ms),0) AS BIGINT) count_executions,
|
||||
MAX(itvl.end_time) last_execution_time,
|
||||
MIN(itvl.start_time) first_execution_time
|
||||
FROM sys.query_store_wait_stats ws
|
||||
JOIN sys.query_store_runtime_stats_interval itvl ON itvl.runtime_stats_interval_id = ws.runtime_stats_interval_id
|
||||
WHERE NOT (itvl.start_time > @interval_end_time OR itvl.end_time < @interval_start_time)
|
||||
GROUP BY ws.plan_id, ws.runtime_stats_interval_id, ws.wait_category
|
||||
)
|
||||
SELECT
|
||||
p.query_id query_id,
|
||||
q.object_id object_id,
|
||||
ISNULL(OBJECT_NAME(q.object_id),'') object_name,
|
||||
qt.query_sql_text query_sql_text,
|
||||
ROUND(CONVERT(float, SQRT( SUM(ws.stdev_query_wait_time*ws.stdev_query_wait_time*ws.count_executions)/NULLIF(SUM(ws.count_executions), 0)))*1,2) stdev_query_wait_time,
|
||||
MAX(ws.count_executions) count_executions,
|
||||
COUNT(distinct p.plan_id) num_plans
|
||||
FROM wait_stats ws
|
||||
JOIN sys.query_store_plan p ON p.plan_id = ws.plan_id
|
||||
JOIN sys.query_store_query q ON q.query_id = p.query_id
|
||||
JOIN sys.query_store_query_text qt ON q.query_text_id = qt.query_text_id
|
||||
WHERE NOT (ws.first_execution_time > @interval_end_time OR ws.last_execution_time < @interval_start_time)
|
||||
GROUP BY p.query_id, qt.query_sql_text, q.object_id
|
||||
HAVING COUNT(distinct p.plan_id) >= 1
|
||||
ORDER BY query_id DESC";
|
||||
|
||||
public const string HandleGetTopResourceConsumersDetailedSummaryReportRequest =
|
||||
@"DECLARE @interval_start_time DATETIMEOFFSET = '2023-06-10T12:34:56.0000000+00:00';
|
||||
DECLARE @interval_end_time DATETIMEOFFSET = '2023-06-17T12:34:56.0000000+00:00';
|
||||
|
||||
With wait_stats AS
|
||||
(
|
||||
SELECT
|
||||
ws.plan_id plan_id,
|
||||
ws.wait_category,
|
||||
ROUND(CONVERT(float, SUM(ws.total_query_wait_time_ms)/SUM(ws.total_query_wait_time_ms/ws.avg_query_wait_time_ms))*1,2) avg_query_wait_time,
|
||||
ROUND(CONVERT(float, SQRT( SUM(ws.stdev_query_wait_time_ms*ws.stdev_query_wait_time_ms*(ws.total_query_wait_time_ms/ws.avg_query_wait_time_ms))/SUM(ws.total_query_wait_time_ms/ws.avg_query_wait_time_ms)))*1,2) stdev_query_wait_time,
|
||||
CAST(ROUND(SUM(ws.total_query_wait_time_ms/ws.avg_query_wait_time_ms),0) AS BIGINT) count_executions,
|
||||
MAX(itvl.end_time) last_execution_time,
|
||||
MIN(itvl.start_time) first_execution_time
|
||||
FROM sys.query_store_wait_stats ws
|
||||
JOIN sys.query_store_runtime_stats_interval itvl ON itvl.runtime_stats_interval_id = ws.runtime_stats_interval_id
|
||||
WHERE NOT (itvl.start_time > @interval_end_time OR itvl.end_time < @interval_start_time)
|
||||
GROUP BY ws.plan_id, ws.runtime_stats_interval_id, ws.wait_category
|
||||
),
|
||||
top_wait_stats AS
|
||||
(
|
||||
SELECT
|
||||
p.query_id query_id,
|
||||
q.object_id object_id,
|
||||
ISNULL(OBJECT_NAME(q.object_id),'') object_name,
|
||||
qt.query_sql_text query_sql_text,
|
||||
ROUND(CONVERT(float, SQRT( SUM(ws.stdev_query_wait_time*ws.stdev_query_wait_time*ws.count_executions)/NULLIF(SUM(ws.count_executions), 0)))*1,2) stdev_query_wait_time,
|
||||
MAX(ws.count_executions) count_executions,
|
||||
COUNT(distinct p.plan_id) num_plans
|
||||
FROM wait_stats ws
|
||||
JOIN sys.query_store_plan p ON p.plan_id = ws.plan_id
|
||||
JOIN sys.query_store_query q ON q.query_id = p.query_id
|
||||
JOIN sys.query_store_query_text qt ON q.query_text_id = qt.query_text_id
|
||||
WHERE NOT (ws.first_execution_time > @interval_end_time OR ws.last_execution_time < @interval_start_time)
|
||||
GROUP BY p.query_id, qt.query_sql_text, q.object_id
|
||||
),
|
||||
top_other_stats AS
|
||||
(
|
||||
SELECT
|
||||
p.query_id query_id,
|
||||
q.object_id object_id,
|
||||
ISNULL(OBJECT_NAME(q.object_id),'') object_name,
|
||||
qt.query_sql_text query_sql_text,
|
||||
ROUND(CONVERT(float, SQRT( SUM(rs.stdev_clr_time*rs.stdev_clr_time*rs.count_executions)/NULLIF(SUM(rs.count_executions), 0)))*0.001,2) stdev_clr_time,
|
||||
ROUND(CONVERT(float, SQRT( SUM(rs.stdev_cpu_time*rs.stdev_cpu_time*rs.count_executions)/NULLIF(SUM(rs.count_executions), 0)))*0.001,2) stdev_cpu_time,
|
||||
ROUND(CONVERT(float, SQRT( SUM(rs.stdev_dop*rs.stdev_dop*rs.count_executions)/NULLIF(SUM(rs.count_executions), 0)))*1,0) stdev_dop,
|
||||
ROUND(CONVERT(float, SQRT( SUM(rs.stdev_duration*rs.stdev_duration*rs.count_executions)/NULLIF(SUM(rs.count_executions), 0)))*0.001,2) stdev_duration,
|
||||
ROUND(CONVERT(float, SQRT( SUM(rs.stdev_logical_io_reads*rs.stdev_logical_io_reads*rs.count_executions)/NULLIF(SUM(rs.count_executions), 0)))*8,2) stdev_logical_io_reads,
|
||||
ROUND(CONVERT(float, SQRT( SUM(rs.stdev_logical_io_writes*rs.stdev_logical_io_writes*rs.count_executions)/NULLIF(SUM(rs.count_executions), 0)))*8,2) stdev_logical_io_writes,
|
||||
ROUND(CONVERT(float, SQRT( SUM(rs.stdev_log_bytes_used*rs.stdev_log_bytes_used*rs.count_executions)/NULLIF(SUM(rs.count_executions), 0)))*0.0009765625,2) stdev_log_bytes_used,
|
||||
ROUND(CONVERT(float, SQRT( SUM(rs.stdev_query_max_used_memory*rs.stdev_query_max_used_memory*rs.count_executions)/NULLIF(SUM(rs.count_executions), 0)))*8,2) stdev_query_max_used_memory,
|
||||
ROUND(CONVERT(float, SQRT( SUM(rs.stdev_physical_io_reads*rs.stdev_physical_io_reads*rs.count_executions)/NULLIF(SUM(rs.count_executions), 0)))*8,2) stdev_physical_io_reads,
|
||||
ROUND(CONVERT(float, SQRT( SUM(rs.stdev_rowcount*rs.stdev_rowcount*rs.count_executions)/NULLIF(SUM(rs.count_executions), 0)))*1,0) stdev_rowcount,
|
||||
ROUND(CONVERT(float, SQRT( SUM(rs.stdev_tempdb_space_used*rs.stdev_tempdb_space_used*rs.count_executions)/NULLIF(SUM(rs.count_executions), 0)))*8,2) stdev_tempdb_space_used,
|
||||
SUM(rs.count_executions) count_executions,
|
||||
COUNT(distinct p.plan_id) num_plans
|
||||
FROM sys.query_store_runtime_stats rs
|
||||
JOIN sys.query_store_plan p ON p.plan_id = rs.plan_id
|
||||
JOIN sys.query_store_query q ON q.query_id = p.query_id
|
||||
JOIN sys.query_store_query_text qt ON q.query_text_id = qt.query_text_id
|
||||
WHERE NOT (rs.first_execution_time > @interval_end_time OR rs.last_execution_time < @interval_start_time)
|
||||
GROUP BY p.query_id, qt.query_sql_text, q.object_id
|
||||
)
|
||||
SELECT
|
||||
A.query_id query_id,
|
||||
A.object_id object_id,
|
||||
A.object_name object_name,
|
||||
A.query_sql_text query_sql_text,
|
||||
A.stdev_clr_time stdev_clr_time,
|
||||
A.stdev_cpu_time stdev_cpu_time,
|
||||
A.stdev_dop stdev_dop,
|
||||
A.stdev_duration stdev_duration,
|
||||
A.stdev_logical_io_reads stdev_logical_io_reads,
|
||||
A.stdev_logical_io_writes stdev_logical_io_writes,
|
||||
A.stdev_log_bytes_used stdev_log_bytes_used,
|
||||
A.stdev_query_max_used_memory stdev_query_max_used_memory,
|
||||
A.stdev_physical_io_reads stdev_physical_io_reads,
|
||||
A.stdev_rowcount stdev_rowcount,
|
||||
A.stdev_tempdb_space_used stdev_tempdb_space_used,
|
||||
ISNULL(B.stdev_query_wait_time,0) stdev_query_wait_time,
|
||||
A.count_executions count_executions,
|
||||
A.num_plans num_plans
|
||||
FROM top_other_stats A LEFT JOIN top_wait_stats B on A.query_id = B.query_id and A.query_sql_text = B.query_sql_text and A.object_id = B.object_id
|
||||
WHERE A.num_plans >= 1
|
||||
ORDER BY query_id DESC";
|
||||
|
||||
public const string HandleGetForcedPlanQueriesReportRequest =
|
||||
@"WITH
|
||||
A AS
|
||||
(
|
||||
SELECT
|
||||
p.query_id query_id,
|
||||
qt.query_sql_text query_sql_text,
|
||||
p.plan_id plan_id,
|
||||
p.force_failure_count force_failure_count,
|
||||
p.last_force_failure_reason_desc last_force_failure_reason_desc,
|
||||
p.last_execution_time last_execution_time,
|
||||
q.object_id object_id,
|
||||
ISNULL(OBJECT_NAME(q.object_id),'') object_name,
|
||||
p.last_compile_start_time last_compile_start_time
|
||||
FROM sys.query_store_plan p
|
||||
JOIN sys.query_store_query q ON q.query_id = p.query_id
|
||||
JOIN sys.query_store_query_text qt ON q.query_text_id = qt.query_text_id
|
||||
where p.is_forced_plan = 1
|
||||
),
|
||||
B AS
|
||||
(
|
||||
SELECT
|
||||
p.query_id query_id,
|
||||
MAX(p.last_execution_time) last_execution_time,
|
||||
COUNT(distinct p.plan_id) num_plans
|
||||
FROM sys.query_store_plan p
|
||||
GROUP BY p.query_id
|
||||
HAVING MAX(CAST(p.is_forced_plan AS tinyint)) = 1
|
||||
)
|
||||
SELECT
|
||||
A.query_id,
|
||||
A.query_sql_text,
|
||||
A.plan_id,
|
||||
A.force_failure_count,
|
||||
A.last_compile_start_time,
|
||||
A.last_force_failure_reason_desc,
|
||||
B.num_plans,
|
||||
B.last_execution_time,
|
||||
A.last_execution_time,
|
||||
A.object_id,
|
||||
A.object_name
|
||||
FROM A JOIN B ON A.query_id = B.query_id
|
||||
WHERE B.num_plans >= 1
|
||||
ORDER BY query_id DESC";
|
||||
|
||||
public const string HandleGetTrackedQueriesReportRequest =
|
||||
@"DECLARE @QuerySearchText NVARCHAR(max) = N'test search text';
|
||||
|
||||
SELECT TOP 500 q.query_id, q.query_text_id, qt.query_sql_text
|
||||
FROM sys.query_store_query_text qt JOIN sys.query_store_query q ON q.query_text_id = qt.query_text_id
|
||||
WHERE qt.query_sql_text LIKE ('%' + @QuerySearchText + '%')";
|
||||
|
||||
public const string HandleGetHighVariationQueriesSummaryReportRequest =
|
||||
@"DECLARE @interval_start_time DATETIMEOFFSET = '2023-06-10T12:34:56.0000000+00:00';
|
||||
DECLARE @interval_end_time DATETIMEOFFSET = '2023-06-17T12:34:56.0000000+00:00';
|
||||
|
||||
With wait_stats AS
|
||||
(
|
||||
SELECT
|
||||
ws.plan_id plan_id,
|
||||
ws.wait_category,
|
||||
ROUND(CONVERT(float, SUM(ws.total_query_wait_time_ms)/SUM(ws.total_query_wait_time_ms/ws.avg_query_wait_time_ms))*1,2) avg_query_wait_time,
|
||||
ROUND(CONVERT(float, MIN(ws.min_query_wait_time_ms))*1,2) min_query_wait_time,
|
||||
ROUND(CONVERT(float, MAX(ws.max_query_wait_time_ms))*1,2) max_query_wait_time,
|
||||
ROUND(CONVERT(float, SQRT( SUM(ws.stdev_query_wait_time_ms*ws.stdev_query_wait_time_ms*(ws.total_query_wait_time_ms/ws.avg_query_wait_time_ms))/SUM(ws.total_query_wait_time_ms/ws.avg_query_wait_time_ms)))*1,2) stdev_query_wait_time,
|
||||
ROUND(CONVERT(float, SUM(ws.total_query_wait_time_ms))*1,2) total_query_wait_time,
|
||||
CAST(ROUND(SUM(ws.total_query_wait_time_ms/ws.avg_query_wait_time_ms),0) AS BIGINT) count_executions,
|
||||
MAX(itvl.end_time) last_execution_time,
|
||||
MIN(itvl.start_time) first_execution_time
|
||||
FROM sys.query_store_wait_stats ws
|
||||
JOIN sys.query_store_runtime_stats_interval itvl ON itvl.runtime_stats_interval_id = ws.runtime_stats_interval_id
|
||||
WHERE NOT (itvl.start_time > @interval_end_time OR itvl.end_time < @interval_start_time)
|
||||
GROUP BY ws.plan_id, ws.runtime_stats_interval_id, ws.wait_category
|
||||
)
|
||||
SELECT
|
||||
p.query_id query_id,
|
||||
q.object_id object_id,
|
||||
ISNULL(OBJECT_NAME(q.object_id),'') object_name,
|
||||
qt.query_sql_text query_sql_text,
|
||||
ROUND(CONVERT(float, SQRT( SUM(ws.stdev_query_wait_time*ws.stdev_query_wait_time*ws.count_executions)/NULLIF(SUM(ws.count_executions), 0)))*1,2) stdev_query_wait_time,
|
||||
ROUND(CONVERT(float, SUM(ws.avg_query_wait_time*ws.count_executions))/NULLIF(SUM(ws.count_executions), 0)*1,2) avg_query_wait_time,
|
||||
MAX(ws.count_executions) count_executions,
|
||||
COUNT(distinct p.plan_id) num_plans
|
||||
FROM wait_stats ws
|
||||
JOIN sys.query_store_plan p ON p.plan_id = ws.plan_id
|
||||
JOIN sys.query_store_query q ON q.query_id = p.query_id
|
||||
JOIN sys.query_store_query_text qt ON q.query_text_id = qt.query_text_id
|
||||
WHERE NOT (ws.first_execution_time > @interval_end_time OR ws.last_execution_time < @interval_start_time)
|
||||
GROUP BY p.query_id, qt.query_sql_text, q.object_id
|
||||
HAVING COUNT(distinct p.plan_id) >= 1 AND SUM(ws.count_executions) > 1
|
||||
ORDER BY query_id DESC";
|
||||
|
||||
public const string HandleGetHighVariationQueriesDetailedSummaryReportRequest =
|
||||
@"DECLARE @interval_start_time DATETIMEOFFSET = '2023-06-10T12:34:56.0000000+00:00';
|
||||
DECLARE @interval_end_time DATETIMEOFFSET = '2023-06-17T12:34:56.0000000+00:00';
|
||||
|
||||
With wait_stats AS
|
||||
(
|
||||
SELECT
|
||||
ws.plan_id plan_id,
|
||||
ws.wait_category,
|
||||
ROUND(CONVERT(float, SUM(ws.total_query_wait_time_ms)/SUM(ws.total_query_wait_time_ms/ws.avg_query_wait_time_ms))*1,2) avg_query_wait_time,
|
||||
ROUND(CONVERT(float, SQRT( SUM(ws.stdev_query_wait_time_ms*ws.stdev_query_wait_time_ms*(ws.total_query_wait_time_ms/ws.avg_query_wait_time_ms))/SUM(ws.total_query_wait_time_ms/ws.avg_query_wait_time_ms)))*1,2) stdev_query_wait_time,
|
||||
CAST(ROUND(SUM(ws.total_query_wait_time_ms/ws.avg_query_wait_time_ms),0) AS BIGINT) count_executions,
|
||||
MAX(itvl.end_time) last_execution_time,
|
||||
MIN(itvl.start_time) first_execution_time
|
||||
FROM sys.query_store_wait_stats ws
|
||||
JOIN sys.query_store_runtime_stats_interval itvl ON itvl.runtime_stats_interval_id = ws.runtime_stats_interval_id
|
||||
WHERE NOT (itvl.start_time > @interval_end_time OR itvl.end_time < @interval_start_time)
|
||||
GROUP BY ws.plan_id, ws.runtime_stats_interval_id, ws.wait_category
|
||||
),
|
||||
wait_stats_variation AS
|
||||
(
|
||||
SELECT
|
||||
p.query_id query_id,
|
||||
q.object_id object_id,
|
||||
ISNULL(OBJECT_NAME(q.object_id),'') object_name,
|
||||
qt.query_sql_text query_sql_text,
|
||||
ROUND(CONVERT(float, SQRT( SUM(ws.stdev_query_wait_time*ws.stdev_query_wait_time*ws.count_executions)/NULLIF(SUM(ws.count_executions), 0)))*1,2) stdev_query_wait_time,
|
||||
MAX(ws.count_executions) count_executions,
|
||||
COUNT(distinct p.plan_id) num_plans
|
||||
FROM wait_stats ws
|
||||
JOIN sys.query_store_plan p ON p.plan_id = ws.plan_id
|
||||
JOIN sys.query_store_query q ON q.query_id = p.query_id
|
||||
JOIN sys.query_store_query_text qt ON q.query_text_id = qt.query_text_id
|
||||
WHERE NOT (ws.first_execution_time > @interval_end_time OR ws.last_execution_time < @interval_start_time)
|
||||
GROUP BY p.query_id, qt.query_sql_text, q.object_id
|
||||
),
|
||||
other_stats_variation AS
|
||||
(
|
||||
SELECT
|
||||
p.query_id query_id,
|
||||
q.object_id object_id,
|
||||
ISNULL(OBJECT_NAME(q.object_id),'') object_name,
|
||||
qt.query_sql_text query_sql_text,
|
||||
ROUND(CONVERT(float, SQRT( SUM(rs.stdev_clr_time*rs.stdev_clr_time*rs.count_executions)/NULLIF(SUM(rs.count_executions), 0)))*0.001,2) stdev_clr_time,
|
||||
ROUND(CONVERT(float, SQRT( SUM(rs.stdev_cpu_time*rs.stdev_cpu_time*rs.count_executions)/NULLIF(SUM(rs.count_executions), 0)))*0.001,2) stdev_cpu_time,
|
||||
ROUND(CONVERT(float, SQRT( SUM(rs.stdev_dop*rs.stdev_dop*rs.count_executions)/NULLIF(SUM(rs.count_executions), 0)))*1,0) stdev_dop,
|
||||
ROUND(CONVERT(float, SQRT( SUM(rs.stdev_duration*rs.stdev_duration*rs.count_executions)/NULLIF(SUM(rs.count_executions), 0)))*0.001,2) stdev_duration,
|
||||
ROUND(CONVERT(float, SQRT( SUM(rs.stdev_logical_io_reads*rs.stdev_logical_io_reads*rs.count_executions)/NULLIF(SUM(rs.count_executions), 0)))*8,2) stdev_logical_io_reads,
|
||||
ROUND(CONVERT(float, SQRT( SUM(rs.stdev_logical_io_writes*rs.stdev_logical_io_writes*rs.count_executions)/NULLIF(SUM(rs.count_executions), 0)))*8,2) stdev_logical_io_writes,
|
||||
ROUND(CONVERT(float, SQRT( SUM(rs.stdev_log_bytes_used*rs.stdev_log_bytes_used*rs.count_executions)/NULLIF(SUM(rs.count_executions), 0)))*0.0009765625,2) stdev_log_bytes_used,
|
||||
ROUND(CONVERT(float, SQRT( SUM(rs.stdev_query_max_used_memory*rs.stdev_query_max_used_memory*rs.count_executions)/NULLIF(SUM(rs.count_executions), 0)))*8,2) stdev_query_max_used_memory,
|
||||
ROUND(CONVERT(float, SQRT( SUM(rs.stdev_physical_io_reads*rs.stdev_physical_io_reads*rs.count_executions)/NULLIF(SUM(rs.count_executions), 0)))*8,2) stdev_physical_io_reads,
|
||||
ROUND(CONVERT(float, SQRT( SUM(rs.stdev_rowcount*rs.stdev_rowcount*rs.count_executions)/NULLIF(SUM(rs.count_executions), 0)))*1,0) stdev_rowcount,
|
||||
ROUND(CONVERT(float, SQRT( SUM(rs.stdev_tempdb_space_used*rs.stdev_tempdb_space_used*rs.count_executions)/NULLIF(SUM(rs.count_executions), 0)))*8,2) stdev_tempdb_space_used,
|
||||
SUM(rs.count_executions) count_executions,
|
||||
COUNT(distinct p.plan_id) num_plans
|
||||
FROM sys.query_store_runtime_stats rs
|
||||
JOIN sys.query_store_plan p ON p.plan_id = rs.plan_id
|
||||
JOIN sys.query_store_query q ON q.query_id = p.query_id
|
||||
JOIN sys.query_store_query_text qt ON q.query_text_id = qt.query_text_id
|
||||
WHERE NOT (rs.first_execution_time > @interval_end_time OR rs.last_execution_time < @interval_start_time)
|
||||
GROUP BY p.query_id, qt.query_sql_text, q.object_id
|
||||
)
|
||||
SELECT
|
||||
A.query_id query_id,
|
||||
A.object_id object_id,
|
||||
A.object_name object_name,
|
||||
A.query_sql_text query_sql_text,
|
||||
A.stdev_clr_time stdev_clr_time,
|
||||
A.stdev_cpu_time stdev_cpu_time,
|
||||
A.stdev_dop stdev_dop,
|
||||
A.stdev_duration stdev_duration,
|
||||
A.stdev_logical_io_reads stdev_logical_io_reads,
|
||||
A.stdev_logical_io_writes stdev_logical_io_writes,
|
||||
A.stdev_log_bytes_used stdev_log_bytes_used,
|
||||
A.stdev_query_max_used_memory stdev_query_max_used_memory,
|
||||
A.stdev_physical_io_reads stdev_physical_io_reads,
|
||||
A.stdev_rowcount stdev_rowcount,
|
||||
A.stdev_tempdb_space_used stdev_tempdb_space_used,
|
||||
ISNULL(B.stdev_query_wait_time,0) stdev_query_wait_time,
|
||||
A.count_executions count_executions,
|
||||
A.num_plans num_plans
|
||||
FROM other_stats_variation A LEFT JOIN wait_stats_variation B on A.query_id = B.query_id and A.query_sql_text = B.query_sql_text and A.object_id = B.object_id
|
||||
WHERE A.num_plans >= 1 AND A.count_executions > 1
|
||||
ORDER BY query_id DESC";
|
||||
|
||||
public const string HandleGetOverallResourceConsumptionReportRequest =
|
||||
@"DECLARE @interval_start_time DATETIMEOFFSET = '2023-06-10T12:34:56.0000000+00:00';
|
||||
DECLARE @interval_end_time DATETIMEOFFSET = '2023-06-17T12:34:56.0000000+00:00';
|
||||
|
||||
WITH DateGenerator AS
|
||||
(
|
||||
SELECT CAST(@interval_start_time AS DATETIME) DatePlaceHolder
|
||||
UNION ALL
|
||||
SELECT DATEADD(hh, 1, DatePlaceHolder)
|
||||
FROM DateGenerator
|
||||
WHERE DATEADD(hh, 1, DatePlaceHolder) < @interval_end_time
|
||||
), WaitStats AS
|
||||
(
|
||||
SELECT
|
||||
ROUND(CONVERT(float, SUM(ws.total_query_wait_time_ms))*1,2) total_query_wait_time
|
||||
FROM sys.query_store_wait_stats ws
|
||||
JOIN sys.query_store_runtime_stats_interval itvl ON itvl.runtime_stats_interval_id = ws.runtime_stats_interval_id
|
||||
WHERE NOT (itvl.start_time > @interval_end_time OR itvl.end_time < @interval_start_time)
|
||||
GROUP BY DATEDIFF(hh, 0, itvl.end_time)
|
||||
),
|
||||
UnionAll AS
|
||||
(
|
||||
SELECT
|
||||
ROUND(CONVERT(float, SUM(rs.avg_clr_time*rs.count_executions))*0.001,2) as total_clr_time,
|
||||
ROUND(CONVERT(float, SUM(rs.avg_cpu_time*rs.count_executions))*0.001,2) as total_cpu_time,
|
||||
ROUND(CONVERT(float, SUM(rs.avg_dop*rs.count_executions))*1,0) as total_dop,
|
||||
ROUND(CONVERT(float, SUM(rs.avg_duration*rs.count_executions))*0.001,2) as total_duration,
|
||||
CONVERT(float, SUM(rs.count_executions)) as total_count_executions,
|
||||
ROUND(CONVERT(float, SUM(rs.avg_logical_io_reads*rs.count_executions))*8,2) as total_logical_io_reads,
|
||||
ROUND(CONVERT(float, SUM(rs.avg_logical_io_writes*rs.count_executions))*8,2) as total_logical_io_writes,
|
||||
ROUND(CONVERT(float, SUM(rs.avg_log_bytes_used*rs.count_executions))*0.0009765625,2) as total_log_bytes_used,
|
||||
ROUND(CONVERT(float, SUM(rs.avg_query_max_used_memory*rs.count_executions))*8,2) as total_query_max_used_memory,
|
||||
ROUND(CONVERT(float, SUM(rs.avg_physical_io_reads*rs.count_executions))*8,2) as total_physical_io_reads,
|
||||
ROUND(CONVERT(float, SUM(rs.avg_rowcount*rs.count_executions))*1,0) as total_rowcount,
|
||||
ROUND(CONVERT(float, SUM(rs.avg_tempdb_space_used*rs.count_executions))*8,2) as total_tempdb_space_used,
|
||||
DATEADD(hh, ((DATEDIFF(hh, 0, rs.last_execution_time))),0 ) as bucket_start,
|
||||
DATEADD(hh, (1 + (DATEDIFF(hh, 0, rs.last_execution_time))), 0) as bucket_end
|
||||
FROM sys.query_store_runtime_stats rs
|
||||
WHERE NOT (rs.first_execution_time > @interval_end_time OR rs.last_execution_time < @interval_start_time)
|
||||
GROUP BY DATEDIFF(hh, 0, rs.last_execution_time)
|
||||
)
|
||||
SELECT
|
||||
total_clr_time,
|
||||
total_cpu_time,
|
||||
total_dop,
|
||||
total_duration,
|
||||
total_count_executions,
|
||||
total_logical_io_reads,
|
||||
total_logical_io_writes,
|
||||
total_log_bytes_used,
|
||||
total_query_max_used_memory,
|
||||
total_physical_io_reads,
|
||||
total_rowcount,
|
||||
total_tempdb_space_used,
|
||||
total_query_wait_time,
|
||||
SWITCHOFFSET(bucket_start, DATEPART(tz, @interval_start_time)) , SWITCHOFFSET(bucket_end, DATEPART(tz, @interval_start_time))
|
||||
FROM
|
||||
(
|
||||
SELECT *, ROW_NUMBER() OVER (PARTITION BY bucket_start ORDER BY bucket_start, total_duration DESC) AS RowNumber
|
||||
FROM UnionAll , WaitStats
|
||||
) as UnionAllResults
|
||||
WHERE UnionAllResults.RowNumber = 1
|
||||
OPTION (MAXRECURSION 0)";
|
||||
|
||||
public const string HandleGetRegressedQueriesSummaryReportRequest =
|
||||
@"DECLARE @recent_start_time DATETIMEOFFSET = '2023-06-17T11:34:56.0000000+00:00';
|
||||
DECLARE @recent_end_time DATETIMEOFFSET = '2023-06-17T12:34:56.0000000+00:00';
|
||||
DECLARE @history_start_time DATETIMEOFFSET = '2023-06-10T12:34:56.0000000+00:00';
|
||||
DECLARE @history_end_time DATETIMEOFFSET = '2023-06-17T12:34:56.0000000+00:00';
|
||||
DECLARE @min_exec_count BIGINT = 1;
|
||||
|
||||
WITH wait_stats AS
|
||||
(
|
||||
SELECT
|
||||
ws.plan_id plan_id,
|
||||
ws.wait_category,
|
||||
ROUND(CONVERT(float, SUM(ws.total_query_wait_time_ms)/SUM(ws.total_query_wait_time_ms/ws.avg_query_wait_time_ms))*1,2) avg_query_wait_time,
|
||||
ROUND(CONVERT(float, MIN(ws.min_query_wait_time_ms))*1,2) min_query_wait_time,
|
||||
ROUND(CONVERT(float, MAX(ws.max_query_wait_time_ms))*1,2) max_query_wait_time,
|
||||
ROUND(CONVERT(float, SQRT( SUM(ws.stdev_query_wait_time_ms*ws.stdev_query_wait_time_ms*(ws.total_query_wait_time_ms/ws.avg_query_wait_time_ms))/SUM(ws.total_query_wait_time_ms/ws.avg_query_wait_time_ms)))*1,2) stdev_query_wait_time,
|
||||
ROUND(CONVERT(float, SUM(ws.total_query_wait_time_ms))*1,2) total_query_wait_time,
|
||||
CAST(ROUND(SUM(ws.total_query_wait_time_ms/ws.avg_query_wait_time_ms),0) AS BIGINT) count_executions,
|
||||
MAX(itvl.end_time) last_execution_time,
|
||||
MIN(itvl.start_time) first_execution_time
|
||||
FROM sys.query_store_wait_stats ws
|
||||
JOIN sys.query_store_runtime_stats_interval itvl ON itvl.runtime_stats_interval_id = ws.runtime_stats_interval_id
|
||||
WHERE NOT (itvl.start_time > @history_end_time OR itvl.end_time < @history_start_time)
|
||||
GROUP BY ws.plan_id, ws.runtime_stats_interval_id, ws.wait_category
|
||||
),
|
||||
hist AS
|
||||
(
|
||||
SELECT
|
||||
p.query_id query_id,
|
||||
ROUND(CONVERT(float, SQRT( SUM(ws.stdev_query_wait_time*ws.stdev_query_wait_time*ws.count_executions)/NULLIF(SUM(ws.count_executions), 0)))*1,2) stdev_query_wait_time,
|
||||
MAX(ws.count_executions) count_executions,
|
||||
COUNT(distinct p.plan_id) num_plans
|
||||
FROM wait_stats ws
|
||||
JOIN sys.query_store_plan p ON p.plan_id = ws.plan_id
|
||||
WHERE NOT (ws.first_execution_time > @history_end_time OR ws.last_execution_time < @history_start_time)
|
||||
GROUP BY p.query_id
|
||||
),
|
||||
recent AS
|
||||
(
|
||||
SELECT
|
||||
p.query_id query_id,
|
||||
ROUND(CONVERT(float, SQRT( SUM(ws.stdev_query_wait_time*ws.stdev_query_wait_time*ws.count_executions)/NULLIF(SUM(ws.count_executions), 0)))*1,2) stdev_query_wait_time,
|
||||
MAX(ws.count_executions) count_executions,
|
||||
COUNT(distinct p.plan_id) num_plans
|
||||
FROM wait_stats ws
|
||||
JOIN sys.query_store_plan p ON p.plan_id = ws.plan_id
|
||||
WHERE NOT (ws.first_execution_time > @recent_end_time OR ws.last_execution_time < @recent_start_time)
|
||||
GROUP BY p.query_id
|
||||
)
|
||||
SELECT
|
||||
results.query_id query_id,
|
||||
results.object_id object_id,
|
||||
ISNULL(OBJECT_NAME(results.object_id),'') object_name,
|
||||
results.query_sql_text query_sql_text,
|
||||
results.query_wait_time_regr_perc_recent query_wait_time_regr_perc_recent,
|
||||
results.stdev_query_wait_time_recent stdev_query_wait_time_recent,
|
||||
results.stdev_query_wait_time_hist stdev_query_wait_time_hist,
|
||||
ISNULL(results.count_executions_recent, 0) count_executions_recent,
|
||||
ISNULL(results.count_executions_hist, 0) count_executions_hist,
|
||||
queries.num_plans num_plans
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
hist.query_id query_id,
|
||||
q.object_id object_id,
|
||||
qt.query_sql_text query_sql_text,
|
||||
ROUND(CONVERT(float, recent.stdev_query_wait_time-hist.stdev_query_wait_time)/NULLIF(hist.stdev_query_wait_time,0)*100.0, 2) query_wait_time_regr_perc_recent,
|
||||
ROUND(recent.stdev_query_wait_time, 2) stdev_query_wait_time_recent,
|
||||
ROUND(hist.stdev_query_wait_time, 2) stdev_query_wait_time_hist,
|
||||
recent.count_executions count_executions_recent,
|
||||
hist.count_executions count_executions_hist
|
||||
FROM hist
|
||||
JOIN recent ON hist.query_id = recent.query_id
|
||||
JOIN sys.query_store_query q ON q.query_id = hist.query_id
|
||||
JOIN sys.query_store_query_text qt ON q.query_text_id = qt.query_text_id
|
||||
WHERE
|
||||
recent.count_executions >= @min_exec_count
|
||||
) AS results
|
||||
JOIN
|
||||
(
|
||||
SELECT
|
||||
p.query_id query_id,
|
||||
COUNT(distinct p.plan_id) num_plans
|
||||
FROM sys.query_store_plan p
|
||||
GROUP BY p.query_id
|
||||
HAVING COUNT(distinct p.plan_id) >= 1
|
||||
) AS queries ON queries.query_id = results.query_id
|
||||
WHERE query_wait_time_regr_perc_recent > 0
|
||||
OPTION (MERGE JOIN)";
|
||||
|
||||
public const string HandleGetRegressedQueriesDetailedSummaryReportRequest =
|
||||
@"DECLARE @recent_start_time DATETIMEOFFSET = '2023-06-17T11:34:56.0000000+00:00';
|
||||
DECLARE @recent_end_time DATETIMEOFFSET = '2023-06-17T12:34:56.0000000+00:00';
|
||||
DECLARE @history_start_time DATETIMEOFFSET = '2023-06-10T12:34:56.0000000+00:00';
|
||||
DECLARE @history_end_time DATETIMEOFFSET = '2023-06-17T12:34:56.0000000+00:00';
|
||||
DECLARE @min_exec_count BIGINT = 1;
|
||||
|
||||
WITH
|
||||
wait_stats AS
|
||||
(
|
||||
SELECT
|
||||
ws.plan_id plan_id,
|
||||
ws.wait_category,
|
||||
ROUND(CONVERT(float, SUM(ws.total_query_wait_time_ms)/SUM(ws.total_query_wait_time_ms/ws.avg_query_wait_time_ms))*1,2) avg_query_wait_time,
|
||||
ROUND(CONVERT(float, MIN(ws.min_query_wait_time_ms))*1,2) min_query_wait_time,
|
||||
ROUND(CONVERT(float, MAX(ws.max_query_wait_time_ms))*1,2) max_query_wait_time,
|
||||
ROUND(CONVERT(float, SQRT( SUM(ws.stdev_query_wait_time_ms*ws.stdev_query_wait_time_ms*(ws.total_query_wait_time_ms/ws.avg_query_wait_time_ms))/SUM(ws.total_query_wait_time_ms/ws.avg_query_wait_time_ms)))*1,2) stdev_query_wait_time,
|
||||
ROUND(CONVERT(float, SUM(ws.total_query_wait_time_ms))*1,2) total_query_wait_time,
|
||||
CAST(ROUND(SUM(ws.total_query_wait_time_ms/ws.avg_query_wait_time_ms),0) AS BIGINT) count_executions,
|
||||
MAX(itvl.end_time) last_execution_time,
|
||||
MIN(itvl.start_time) first_execution_time
|
||||
FROM sys.query_store_wait_stats ws
|
||||
JOIN sys.query_store_runtime_stats_interval itvl ON itvl.runtime_stats_interval_id = ws.runtime_stats_interval_id
|
||||
WHERE NOT (itvl.start_time > @history_end_time OR itvl.end_time < @history_start_time)
|
||||
GROUP BY ws.plan_id, ws.runtime_stats_interval_id, ws.wait_category
|
||||
),
|
||||
wait_stats_hist AS
|
||||
(
|
||||
SELECT
|
||||
p.query_id query_id,
|
||||
ROUND(CONVERT(float, SQRT( SUM(ws.stdev_query_wait_time*ws.stdev_query_wait_time*ws.count_executions)/NULLIF(SUM(ws.count_executions), 0)))*1,2) stdev_query_wait_time,
|
||||
MAX(ws.count_executions) count_executions,
|
||||
COUNT(distinct p.plan_id) num_plans
|
||||
FROM wait_stats ws
|
||||
JOIN sys.query_store_plan p ON p.plan_id = ws.plan_id
|
||||
WHERE NOT (ws.first_execution_time > @history_end_time OR ws.last_execution_time < @history_start_time)
|
||||
GROUP BY p.query_id
|
||||
),
|
||||
other_hist AS
|
||||
(
|
||||
SELECT
|
||||
p.query_id query_id,
|
||||
ROUND(CONVERT(float, SQRT( SUM(rs.stdev_clr_time*rs.stdev_clr_time*rs.count_executions)/NULLIF(SUM(rs.count_executions), 0)))*0.001,2) stdev_clr_time,
|
||||
ROUND(CONVERT(float, SQRT( SUM(rs.stdev_cpu_time*rs.stdev_cpu_time*rs.count_executions)/NULLIF(SUM(rs.count_executions), 0)))*0.001,2) stdev_cpu_time,
|
||||
ROUND(CONVERT(float, SQRT( SUM(rs.stdev_dop*rs.stdev_dop*rs.count_executions)/NULLIF(SUM(rs.count_executions), 0)))*1,0) stdev_dop,
|
||||
ROUND(CONVERT(float, SQRT( SUM(rs.stdev_duration*rs.stdev_duration*rs.count_executions)/NULLIF(SUM(rs.count_executions), 0)))*0.001,2) stdev_duration,
|
||||
ROUND(CONVERT(float, SQRT( SUM(rs.stdev_logical_io_reads*rs.stdev_logical_io_reads*rs.count_executions)/NULLIF(SUM(rs.count_executions), 0)))*8,2) stdev_logical_io_reads,
|
||||
ROUND(CONVERT(float, SQRT( SUM(rs.stdev_logical_io_writes*rs.stdev_logical_io_writes*rs.count_executions)/NULLIF(SUM(rs.count_executions), 0)))*8,2) stdev_logical_io_writes,
|
||||
ROUND(CONVERT(float, SQRT( SUM(rs.stdev_log_bytes_used*rs.stdev_log_bytes_used*rs.count_executions)/NULLIF(SUM(rs.count_executions), 0)))*0.0009765625,2) stdev_log_bytes_used,
|
||||
ROUND(CONVERT(float, SQRT( SUM(rs.stdev_query_max_used_memory*rs.stdev_query_max_used_memory*rs.count_executions)/NULLIF(SUM(rs.count_executions), 0)))*8,2) stdev_query_max_used_memory,
|
||||
ROUND(CONVERT(float, SQRT( SUM(rs.stdev_physical_io_reads*rs.stdev_physical_io_reads*rs.count_executions)/NULLIF(SUM(rs.count_executions), 0)))*8,2) stdev_physical_io_reads,
|
||||
ROUND(CONVERT(float, SQRT( SUM(rs.stdev_rowcount*rs.stdev_rowcount*rs.count_executions)/NULLIF(SUM(rs.count_executions), 0)))*1,0) stdev_rowcount,
|
||||
ROUND(CONVERT(float, SQRT( SUM(rs.stdev_tempdb_space_used*rs.stdev_tempdb_space_used*rs.count_executions)/NULLIF(SUM(rs.count_executions), 0)))*8,2) stdev_tempdb_space_used,
|
||||
SUM(rs.count_executions) count_executions,
|
||||
COUNT(distinct p.plan_id) num_plans
|
||||
FROM sys.query_store_runtime_stats rs
|
||||
JOIN sys.query_store_plan p ON p.plan_id = rs.plan_id
|
||||
WHERE NOT (rs.first_execution_time > @history_end_time OR rs.last_execution_time < @history_start_time)
|
||||
GROUP BY p.query_id
|
||||
),
|
||||
hist AS
|
||||
(
|
||||
SELECT
|
||||
other_hist.query_id,
|
||||
other_hist.stdev_clr_time stdev_clr_time,
|
||||
other_hist.stdev_cpu_time stdev_cpu_time,
|
||||
other_hist.stdev_dop stdev_dop,
|
||||
other_hist.stdev_duration stdev_duration,
|
||||
other_hist.stdev_logical_io_reads stdev_logical_io_reads,
|
||||
other_hist.stdev_logical_io_writes stdev_logical_io_writes,
|
||||
other_hist.stdev_log_bytes_used stdev_log_bytes_used,
|
||||
other_hist.stdev_query_max_used_memory stdev_query_max_used_memory,
|
||||
other_hist.stdev_physical_io_reads stdev_physical_io_reads,
|
||||
other_hist.stdev_rowcount stdev_rowcount,
|
||||
other_hist.stdev_tempdb_space_used stdev_tempdb_space_used,
|
||||
ISNULL(wait_stats_hist.stdev_query_wait_time, 0) stdev_query_wait_time,
|
||||
other_hist.count_executions,
|
||||
wait_stats_hist.count_executions wait_stats_count_executions,
|
||||
other_hist.num_plans
|
||||
FROM other_hist
|
||||
LEFT JOIN wait_stats_hist ON wait_stats_hist.query_id = other_hist.query_id
|
||||
),
|
||||
wait_stats_recent AS
|
||||
(
|
||||
SELECT
|
||||
p.query_id query_id,
|
||||
ROUND(CONVERT(float, SQRT( SUM(ws.stdev_query_wait_time*ws.stdev_query_wait_time*ws.count_executions)/NULLIF(SUM(ws.count_executions), 0)))*1,2) stdev_query_wait_time,
|
||||
MAX(ws.count_executions) count_executions,
|
||||
COUNT(distinct p.plan_id) num_plans
|
||||
FROM wait_stats ws
|
||||
JOIN sys.query_store_plan p ON p.plan_id = ws.plan_id
|
||||
WHERE NOT (ws.first_execution_time > @recent_end_time OR ws.last_execution_time < @recent_start_time)
|
||||
GROUP BY p.query_id
|
||||
),
|
||||
other_recent AS
|
||||
(
|
||||
SELECT
|
||||
p.query_id query_id,
|
||||
ROUND(CONVERT(float, SQRT( SUM(rs.stdev_clr_time*rs.stdev_clr_time*rs.count_executions)/NULLIF(SUM(rs.count_executions), 0)))*0.001,2) stdev_clr_time,
|
||||
ROUND(CONVERT(float, SQRT( SUM(rs.stdev_cpu_time*rs.stdev_cpu_time*rs.count_executions)/NULLIF(SUM(rs.count_executions), 0)))*0.001,2) stdev_cpu_time,
|
||||
ROUND(CONVERT(float, SQRT( SUM(rs.stdev_dop*rs.stdev_dop*rs.count_executions)/NULLIF(SUM(rs.count_executions), 0)))*1,0) stdev_dop,
|
||||
ROUND(CONVERT(float, SQRT( SUM(rs.stdev_duration*rs.stdev_duration*rs.count_executions)/NULLIF(SUM(rs.count_executions), 0)))*0.001,2) stdev_duration,
|
||||
ROUND(CONVERT(float, SQRT( SUM(rs.stdev_logical_io_reads*rs.stdev_logical_io_reads*rs.count_executions)/NULLIF(SUM(rs.count_executions), 0)))*8,2) stdev_logical_io_reads,
|
||||
ROUND(CONVERT(float, SQRT( SUM(rs.stdev_logical_io_writes*rs.stdev_logical_io_writes*rs.count_executions)/NULLIF(SUM(rs.count_executions), 0)))*8,2) stdev_logical_io_writes,
|
||||
ROUND(CONVERT(float, SQRT( SUM(rs.stdev_log_bytes_used*rs.stdev_log_bytes_used*rs.count_executions)/NULLIF(SUM(rs.count_executions), 0)))*0.0009765625,2) stdev_log_bytes_used,
|
||||
ROUND(CONVERT(float, SQRT( SUM(rs.stdev_query_max_used_memory*rs.stdev_query_max_used_memory*rs.count_executions)/NULLIF(SUM(rs.count_executions), 0)))*8,2) stdev_query_max_used_memory,
|
||||
ROUND(CONVERT(float, SQRT( SUM(rs.stdev_physical_io_reads*rs.stdev_physical_io_reads*rs.count_executions)/NULLIF(SUM(rs.count_executions), 0)))*8,2) stdev_physical_io_reads,
|
||||
ROUND(CONVERT(float, SQRT( SUM(rs.stdev_rowcount*rs.stdev_rowcount*rs.count_executions)/NULLIF(SUM(rs.count_executions), 0)))*1,0) stdev_rowcount,
|
||||
ROUND(CONVERT(float, SQRT( SUM(rs.stdev_tempdb_space_used*rs.stdev_tempdb_space_used*rs.count_executions)/NULLIF(SUM(rs.count_executions), 0)))*8,2) stdev_tempdb_space_used,
|
||||
SUM(rs.count_executions) count_executions,
|
||||
COUNT(distinct p.plan_id) num_plans
|
||||
FROM sys.query_store_runtime_stats rs
|
||||
JOIN sys.query_store_plan p ON p.plan_id = rs.plan_id
|
||||
WHERE NOT (rs.first_execution_time > @recent_end_time OR rs.last_execution_time < @recent_start_time)
|
||||
GROUP BY p.query_id
|
||||
),
|
||||
recent AS
|
||||
(
|
||||
SELECT
|
||||
other_recent.query_id,
|
||||
other_recent.stdev_clr_time stdev_clr_time,
|
||||
other_recent.stdev_cpu_time stdev_cpu_time,
|
||||
other_recent.stdev_dop stdev_dop,
|
||||
other_recent.stdev_duration stdev_duration,
|
||||
other_recent.stdev_logical_io_reads stdev_logical_io_reads,
|
||||
other_recent.stdev_logical_io_writes stdev_logical_io_writes,
|
||||
other_recent.stdev_log_bytes_used stdev_log_bytes_used,
|
||||
other_recent.stdev_query_max_used_memory stdev_query_max_used_memory,
|
||||
other_recent.stdev_physical_io_reads stdev_physical_io_reads,
|
||||
other_recent.stdev_rowcount stdev_rowcount,
|
||||
other_recent.stdev_tempdb_space_used stdev_tempdb_space_used,
|
||||
ISNULL(wait_stats_recent.stdev_query_wait_time, 0) stdev_query_wait_time,
|
||||
other_recent.count_executions,
|
||||
wait_stats_recent.count_executions wait_stats_count_executions,
|
||||
other_recent.num_plans
|
||||
FROM other_recent
|
||||
LEFT JOIN wait_stats_recent ON wait_stats_recent.query_id = other_recent.query_id
|
||||
)
|
||||
SELECT
|
||||
results.query_id query_id,
|
||||
results.object_id object_id,
|
||||
ISNULL(OBJECT_NAME(results.object_id),'') object_name,
|
||||
results.query_sql_text query_sql_text,
|
||||
results.clr_time_regr_perc_recent clr_time_regr_perc_recent,
|
||||
results.stdev_clr_time_recent stdev_clr_time_recent,
|
||||
results.stdev_clr_time_hist stdev_clr_time_hist,
|
||||
results.cpu_time_regr_perc_recent cpu_time_regr_perc_recent,
|
||||
results.stdev_cpu_time_recent stdev_cpu_time_recent,
|
||||
results.stdev_cpu_time_hist stdev_cpu_time_hist,
|
||||
results.dop_regr_perc_recent dop_regr_perc_recent,
|
||||
results.stdev_dop_recent stdev_dop_recent,
|
||||
results.stdev_dop_hist stdev_dop_hist,
|
||||
results.duration_regr_perc_recent duration_regr_perc_recent,
|
||||
results.stdev_duration_recent stdev_duration_recent,
|
||||
results.stdev_duration_hist stdev_duration_hist,
|
||||
results.logical_io_reads_regr_perc_recent logical_io_reads_regr_perc_recent,
|
||||
results.stdev_logical_io_reads_recent stdev_logical_io_reads_recent,
|
||||
results.stdev_logical_io_reads_hist stdev_logical_io_reads_hist,
|
||||
results.logical_io_writes_regr_perc_recent logical_io_writes_regr_perc_recent,
|
||||
results.stdev_logical_io_writes_recent stdev_logical_io_writes_recent,
|
||||
results.stdev_logical_io_writes_hist stdev_logical_io_writes_hist,
|
||||
results.log_bytes_used_regr_perc_recent log_bytes_used_regr_perc_recent,
|
||||
results.stdev_log_bytes_used_recent stdev_log_bytes_used_recent,
|
||||
results.stdev_log_bytes_used_hist stdev_log_bytes_used_hist,
|
||||
results.query_max_used_memory_regr_perc_recent query_max_used_memory_regr_perc_recent,
|
||||
results.stdev_query_max_used_memory_recent stdev_query_max_used_memory_recent,
|
||||
results.stdev_query_max_used_memory_hist stdev_query_max_used_memory_hist,
|
||||
results.physical_io_reads_regr_perc_recent physical_io_reads_regr_perc_recent,
|
||||
results.stdev_physical_io_reads_recent stdev_physical_io_reads_recent,
|
||||
results.stdev_physical_io_reads_hist stdev_physical_io_reads_hist,
|
||||
results.rowcount_regr_perc_recent rowcount_regr_perc_recent,
|
||||
results.stdev_rowcount_recent stdev_rowcount_recent,
|
||||
results.stdev_rowcount_hist stdev_rowcount_hist,
|
||||
results.tempdb_space_used_regr_perc_recent tempdb_space_used_regr_perc_recent,
|
||||
results.stdev_tempdb_space_used_recent stdev_tempdb_space_used_recent,
|
||||
results.stdev_tempdb_space_used_hist stdev_tempdb_space_used_hist,
|
||||
results.query_wait_time_regr_perc_recent query_wait_time_regr_perc_recent,
|
||||
results.stdev_query_wait_time_recent stdev_query_wait_time_recent,
|
||||
results.stdev_query_wait_time_hist stdev_query_wait_time_hist,
|
||||
ISNULL(results.count_executions_recent, 0) count_executions_recent,
|
||||
ISNULL(results.count_executions_hist, 0) count_executions_hist,
|
||||
queries.num_plans num_plans
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
hist.query_id query_id,
|
||||
q.object_id object_id,
|
||||
qt.query_sql_text query_sql_text,
|
||||
ROUND(CONVERT(float, recent.stdev_clr_time-hist.stdev_clr_time)/NULLIF(hist.stdev_clr_time,0)*100.0, 2) clr_time_regr_perc_recent,
|
||||
ROUND(recent.stdev_clr_time, 2) stdev_clr_time_recent,
|
||||
ROUND(hist.stdev_clr_time, 2) stdev_clr_time_hist,
|
||||
ROUND(CONVERT(float, recent.stdev_cpu_time-hist.stdev_cpu_time)/NULLIF(hist.stdev_cpu_time,0)*100.0, 2) cpu_time_regr_perc_recent,
|
||||
ROUND(recent.stdev_cpu_time, 2) stdev_cpu_time_recent,
|
||||
ROUND(hist.stdev_cpu_time, 2) stdev_cpu_time_hist,
|
||||
ROUND(CONVERT(float, recent.stdev_dop-hist.stdev_dop)/NULLIF(hist.stdev_dop,0)*100.0, 2) dop_regr_perc_recent,
|
||||
ROUND(recent.stdev_dop, 2) stdev_dop_recent,
|
||||
ROUND(hist.stdev_dop, 2) stdev_dop_hist,
|
||||
ROUND(CONVERT(float, recent.stdev_duration-hist.stdev_duration)/NULLIF(hist.stdev_duration,0)*100.0, 2) duration_regr_perc_recent,
|
||||
ROUND(recent.stdev_duration, 2) stdev_duration_recent,
|
||||
ROUND(hist.stdev_duration, 2) stdev_duration_hist,
|
||||
ROUND(CONVERT(float, recent.stdev_logical_io_reads-hist.stdev_logical_io_reads)/NULLIF(hist.stdev_logical_io_reads,0)*100.0, 2) logical_io_reads_regr_perc_recent,
|
||||
ROUND(recent.stdev_logical_io_reads, 2) stdev_logical_io_reads_recent,
|
||||
ROUND(hist.stdev_logical_io_reads, 2) stdev_logical_io_reads_hist,
|
||||
ROUND(CONVERT(float, recent.stdev_logical_io_writes-hist.stdev_logical_io_writes)/NULLIF(hist.stdev_logical_io_writes,0)*100.0, 2) logical_io_writes_regr_perc_recent,
|
||||
ROUND(recent.stdev_logical_io_writes, 2) stdev_logical_io_writes_recent,
|
||||
ROUND(hist.stdev_logical_io_writes, 2) stdev_logical_io_writes_hist,
|
||||
ROUND(CONVERT(float, recent.stdev_log_bytes_used-hist.stdev_log_bytes_used)/NULLIF(hist.stdev_log_bytes_used,0)*100.0, 2) log_bytes_used_regr_perc_recent,
|
||||
ROUND(recent.stdev_log_bytes_used, 2) stdev_log_bytes_used_recent,
|
||||
ROUND(hist.stdev_log_bytes_used, 2) stdev_log_bytes_used_hist,
|
||||
ROUND(CONVERT(float, recent.stdev_query_max_used_memory-hist.stdev_query_max_used_memory)/NULLIF(hist.stdev_query_max_used_memory,0)*100.0, 2) query_max_used_memory_regr_perc_recent,
|
||||
ROUND(recent.stdev_query_max_used_memory, 2) stdev_query_max_used_memory_recent,
|
||||
ROUND(hist.stdev_query_max_used_memory, 2) stdev_query_max_used_memory_hist,
|
||||
ROUND(CONVERT(float, recent.stdev_physical_io_reads-hist.stdev_physical_io_reads)/NULLIF(hist.stdev_physical_io_reads,0)*100.0, 2) physical_io_reads_regr_perc_recent,
|
||||
ROUND(recent.stdev_physical_io_reads, 2) stdev_physical_io_reads_recent,
|
||||
ROUND(hist.stdev_physical_io_reads, 2) stdev_physical_io_reads_hist,
|
||||
ROUND(CONVERT(float, recent.stdev_rowcount-hist.stdev_rowcount)/NULLIF(hist.stdev_rowcount,0)*100.0, 2) rowcount_regr_perc_recent,
|
||||
ROUND(recent.stdev_rowcount, 2) stdev_rowcount_recent,
|
||||
ROUND(hist.stdev_rowcount, 2) stdev_rowcount_hist,
|
||||
ROUND(CONVERT(float, recent.stdev_tempdb_space_used-hist.stdev_tempdb_space_used)/NULLIF(hist.stdev_tempdb_space_used,0)*100.0, 2) tempdb_space_used_regr_perc_recent,
|
||||
ROUND(recent.stdev_tempdb_space_used, 2) stdev_tempdb_space_used_recent,
|
||||
ROUND(hist.stdev_tempdb_space_used, 2) stdev_tempdb_space_used_hist,
|
||||
ROUND(CONVERT(float, recent.stdev_query_wait_time-hist.stdev_query_wait_time)/NULLIF(hist.stdev_query_wait_time,0)*100.0, 2) query_wait_time_regr_perc_recent,
|
||||
ROUND(recent.stdev_query_wait_time, 2) stdev_query_wait_time_recent,
|
||||
ROUND(hist.stdev_query_wait_time, 2) stdev_query_wait_time_hist,
|
||||
recent.count_executions count_executions_recent,
|
||||
hist.count_executions count_executions_hist
|
||||
FROM hist
|
||||
JOIN recent ON hist.query_id = recent.query_id
|
||||
JOIN sys.query_store_query q ON q.query_id = hist.query_id
|
||||
JOIN sys.query_store_query_text qt ON q.query_text_id = qt.query_text_id
|
||||
WHERE
|
||||
recent.count_executions >= @min_exec_count
|
||||
) AS results
|
||||
JOIN
|
||||
(
|
||||
SELECT
|
||||
p.query_id query_id,
|
||||
COUNT(distinct p.plan_id) num_plans
|
||||
FROM sys.query_store_plan p
|
||||
GROUP BY p.query_id
|
||||
HAVING COUNT(distinct p.plan_id) >= 1
|
||||
) AS queries ON queries.query_id = results.query_id
|
||||
OPTION (MERGE JOIN)";
|
||||
|
||||
public const string HandleGetPlanSummaryChartViewRequest =
|
||||
@"DECLARE @query_id BIGINT = 97;
|
||||
DECLARE @interval_start_time DATETIMEOFFSET = '2023-06-10T12:34:56.0000000+00:00';
|
||||
DECLARE @interval_end_time DATETIMEOFFSET = '2023-06-17T12:34:56.0000000+00:00';
|
||||
|
||||
WITH wait_stats AS
|
||||
(
|
||||
SELECT
|
||||
ws.plan_id plan_id,
|
||||
ws.execution_type,
|
||||
ROUND(CONVERT(float, SUM(ws.total_query_wait_time_ms)/SUM(ws.total_query_wait_time_ms/ws.avg_query_wait_time_ms))*1,2) avg_query_wait_time,
|
||||
ROUND(CONVERT(float, MIN(ws.min_query_wait_time_ms))*1,2) min_query_wait_time,
|
||||
ROUND(CONVERT(float, MAX(ws.max_query_wait_time_ms))*1,2) max_query_wait_time,
|
||||
ROUND(CONVERT(float, SQRT( SUM(ws.stdev_query_wait_time_ms*ws.stdev_query_wait_time_ms*(ws.total_query_wait_time_ms/ws.avg_query_wait_time_ms))/SUM(ws.total_query_wait_time_ms/ws.avg_query_wait_time_ms)))*1,2) stdev_query_wait_time,
|
||||
ROUND(CONVERT(float, SUM(ws.total_query_wait_time_ms))*1,2) total_query_wait_time,
|
||||
CAST(ROUND(SUM(ws.total_query_wait_time_ms/ws.avg_query_wait_time_ms),0) AS BIGINT) count_executions,
|
||||
MAX(itvl.end_time) last_execution_time,
|
||||
MIN(itvl.start_time) first_execution_time
|
||||
FROM
|
||||
(
|
||||
SELECT *, LAST_VALUE(last_query_wait_time_ms) OVER (order by plan_id, runtime_stats_interval_id, execution_type, wait_category) last_query_wait_time
|
||||
FROM sys.query_store_wait_stats
|
||||
)
|
||||
AS ws
|
||||
JOIN sys.query_store_runtime_stats_interval itvl ON itvl.runtime_stats_interval_id = ws.runtime_stats_interval_id
|
||||
WHERE NOT (itvl.start_time > @interval_end_time OR itvl.end_time < @interval_start_time)
|
||||
GROUP BY ws.plan_id, ws.runtime_stats_interval_id, ws.execution_type, ws.wait_category
|
||||
),
|
||||
bucketizer as
|
||||
(
|
||||
SELECT
|
||||
ws.plan_id as plan_id,
|
||||
ws.execution_type as execution_type,
|
||||
MAX(ws.count_executions) count_executions,
|
||||
DATEADD(d, ((DATEDIFF(d, 0, ws.last_execution_time))),0 ) as bucket_start,
|
||||
DATEADD(d, (1 + (DATEDIFF(d, 0, ws.last_execution_time))), 0) as bucket_end,
|
||||
ROUND(CONVERT(float, SUM(ws.avg_query_wait_time*ws.count_executions))/NULLIF(SUM(ws.count_executions), 0)*1,2) as avg_query_wait_time,
|
||||
ROUND(CONVERT(float, MAX(ws.max_query_wait_time))*1,2) as max_query_wait_time,
|
||||
ROUND(CONVERT(float, MIN(ws.min_query_wait_time))*1,2) as min_query_wait_time,
|
||||
ROUND(CONVERT(float, SQRT( SUM(ws.stdev_query_wait_time*ws.stdev_query_wait_time*ws.count_executions)/NULLIF(SUM(ws.count_executions), 0)))*1,2) as stdev_query_wait_time,
|
||||
ISNULL(ROUND(CONVERT(float, (SQRT( SUM(ws.stdev_query_wait_time*ws.stdev_query_wait_time*ws.count_executions)/NULLIF(SUM(ws.count_executions), 0))*SUM(ws.count_executions)) / NULLIF(SUM(ws.avg_query_wait_time*ws.count_executions), 0)),2), 0) as variation_query_wait_time,
|
||||
ROUND(CONVERT(float, SUM(ws.avg_query_wait_time*ws.count_executions))*1,2) as total_query_wait_time
|
||||
FROM
|
||||
wait_stats ws
|
||||
JOIN sys.query_store_plan p ON p.plan_id = ws.plan_id
|
||||
WHERE
|
||||
p.query_id = @query_id
|
||||
AND NOT (ws.first_execution_time > @interval_end_time OR ws.last_execution_time < @interval_start_time)
|
||||
GROUP BY
|
||||
ws.plan_id,
|
||||
ws.execution_type,
|
||||
DATEDIFF(d, 0, ws.last_execution_time)
|
||||
),
|
||||
is_forced as
|
||||
(
|
||||
SELECT is_forced_plan, plan_id
|
||||
FROM sys.query_store_plan
|
||||
)
|
||||
SELECT b.plan_id as plan_id,
|
||||
is_forced_plan,
|
||||
execution_type,
|
||||
count_executions,
|
||||
SWITCHOFFSET(bucket_start, DATEPART(tz, @interval_start_time)) AS bucket_start,
|
||||
SWITCHOFFSET(bucket_end, DATEPART(tz, @interval_start_time)) AS bucket_end,
|
||||
avg_query_wait_time,
|
||||
max_query_wait_time,
|
||||
min_query_wait_time,
|
||||
stdev_query_wait_time,
|
||||
variation_query_wait_time,
|
||||
total_query_wait_time
|
||||
FROM bucketizer b
|
||||
JOIN is_forced f ON f.plan_id = b.plan_id";
|
||||
|
||||
public const string HandleGetPlanSummaryGridViewRequest =
|
||||
@"DECLARE @query_id BIGINT = 97;
|
||||
DECLARE @interval_start_time DATETIMEOFFSET = '2023-06-10T12:34:56.0000000+00:00';
|
||||
DECLARE @interval_end_time DATETIMEOFFSET = '2023-06-17T12:34:56.0000000+00:00';
|
||||
|
||||
WITH wait_stats AS
|
||||
(
|
||||
SELECT
|
||||
ws.plan_id plan_id,
|
||||
ws.execution_type,
|
||||
ROUND(CONVERT(float, SUM(ws.total_query_wait_time_ms)/SUM(ws.total_query_wait_time_ms/ws.avg_query_wait_time_ms))*1,2) avg_query_wait_time,
|
||||
ROUND(CONVERT(float, MIN(ws.min_query_wait_time_ms))*1,2) min_query_wait_time,
|
||||
ROUND(CONVERT(float, MAX(ws.max_query_wait_time_ms))*1,2) max_query_wait_time,
|
||||
ROUND(CONVERT(float, SQRT( SUM(ws.stdev_query_wait_time_ms*ws.stdev_query_wait_time_ms*(ws.total_query_wait_time_ms/ws.avg_query_wait_time_ms))/SUM(ws.total_query_wait_time_ms/ws.avg_query_wait_time_ms)))*1,2) stdev_query_wait_time,
|
||||
ROUND(CONVERT(float, SUM(ws.total_query_wait_time_ms))*1,2) total_query_wait_time,
|
||||
ROUND(CONVERT(float, MIN(ws.last_query_wait_time))*1,2) last_query_wait_time,
|
||||
CAST(ROUND(SUM(ws.total_query_wait_time_ms/ws.avg_query_wait_time_ms),0) AS BIGINT) count_executions,
|
||||
MAX(itvl.end_time) last_execution_time,
|
||||
MIN(itvl.start_time) first_execution_time
|
||||
FROM
|
||||
(
|
||||
SELECT *, LAST_VALUE(last_query_wait_time_ms) OVER (order by plan_id, runtime_stats_interval_id, execution_type, wait_category) last_query_wait_time
|
||||
FROM sys.query_store_wait_stats
|
||||
)
|
||||
AS ws
|
||||
JOIN sys.query_store_runtime_stats_interval itvl ON itvl.runtime_stats_interval_id = ws.runtime_stats_interval_id
|
||||
WHERE NOT (itvl.start_time > @interval_end_time OR itvl.end_time < @interval_start_time)
|
||||
GROUP BY ws.plan_id, ws.runtime_stats_interval_id, ws.execution_type, ws.wait_category
|
||||
),
|
||||
last_table AS
|
||||
(
|
||||
SELECT
|
||||
p.plan_id plan_id,
|
||||
first_value(ws.last_query_wait_time) OVER (PARTITION BY p.plan_id ORDER BY ws.last_execution_time DESC) last_value
|
||||
FROM
|
||||
wait_stats ws
|
||||
JOIN
|
||||
sys.query_store_plan p ON p.plan_id = ws.plan_id
|
||||
WHERE
|
||||
p.query_id = @query_id
|
||||
)
|
||||
SELECT p.plan_id,
|
||||
MAX(CONVERT(int, p.is_forced_plan)) is_forced_plan,
|
||||
SUM(distinct ws.execution_type) execution_type,
|
||||
MAX(ws.count_executions) count_executions,
|
||||
ROUND(ROUND(CONVERT(float, MIN(ws.min_query_wait_time))*1,2), 2) min_query_wait_time,
|
||||
ROUND(ROUND(CONVERT(float, MAX(ws.max_query_wait_time))*1,2), 2) max_query_wait_time,
|
||||
ROUND(ROUND(CONVERT(float, SUM(ws.avg_query_wait_time*ws.count_executions))/NULLIF(SUM(ws.count_executions), 0)*1,2), 2) avg_query_wait_time,
|
||||
ROUND(ROUND(CONVERT(float, SQRT( SUM(ws.stdev_query_wait_time*ws.stdev_query_wait_time*ws.count_executions)/NULLIF(SUM(ws.count_executions), 0)))*1,2), 2) stdev_query_wait_time,
|
||||
ROUND(ISNULL(ROUND(CONVERT(float, (SQRT( SUM(ws.stdev_query_wait_time*ws.stdev_query_wait_time*ws.count_executions)/NULLIF(SUM(ws.count_executions), 0))*SUM(ws.count_executions)) / NULLIF(SUM(ws.avg_query_wait_time*ws.count_executions), 0)),2), 0), 2) variation_query_wait_time,
|
||||
ROUND(max(l.last_value), 2) last_query_wait_time,
|
||||
ROUND(ROUND(CONVERT(float, SUM(ws.avg_query_wait_time*ws.count_executions))*1,2), 2) total_query_wait_time,
|
||||
SWITCHOFFSET(MIN(ws.first_execution_time), DATEPART(tz, @interval_start_time)) first_execution_time,
|
||||
SWITCHOFFSET(MAX(ws.last_execution_time), DATEPART(tz, @interval_start_time)) last_execution_time
|
||||
FROM
|
||||
wait_stats ws
|
||||
JOIN
|
||||
sys.query_store_plan p ON p.plan_id = ws.plan_id
|
||||
JOIN
|
||||
last_table l ON p.plan_id = l.plan_id
|
||||
WHERE p.query_id = @query_id
|
||||
AND NOT (ws.first_execution_time > @interval_end_time OR ws.last_execution_time < @interval_start_time)
|
||||
GROUP BY p.plan_id, ws.execution_type
|
||||
ORDER BY count_executions DESC";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlServer.Management.QueryStoreModel.Common;
|
||||
using Microsoft.SqlTools.ServiceLayer.IntegrationTests.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryStore;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryStore.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Common.RequestContextMocking;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using static Microsoft.SqlServer.Management.QueryStoreModel.PlanSummary.PlanSummaryConfiguration;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.QueryStore
|
||||
{
|
||||
public class QueryStoreTests : TestBase
|
||||
{
|
||||
private const string TestConnectionOwnerUri = "FakeConnectionOwnerUri";
|
||||
private static DateTimeOffset TestWindowStart = DateTimeOffset.Parse("6/10/2023 12:34:56 PM +0:00");
|
||||
private static DateTimeOffset TestWindowEnd = TestWindowStart.AddDays(7);
|
||||
private static DateTimeOffset TestWindowRecentStart = TestWindowEnd.AddHours(-1);
|
||||
private static BasicTimeInterval TestTimeInterval => new BasicTimeInterval()
|
||||
{
|
||||
StartDateTimeInUtc = TestWindowStart.ToString("O"),
|
||||
EndDateTimeInUtc = TestWindowEnd.ToString("O")
|
||||
};
|
||||
|
||||
private static BasicTimeInterval RecentTestTimeInterval => new BasicTimeInterval()
|
||||
{
|
||||
StartDateTimeInUtc = TestWindowRecentStart.ToString("O"),
|
||||
EndDateTimeInUtc = TestWindowEnd.ToString("O")
|
||||
};
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
QueryStoreCommonConfiguration.DisplayTimeKind = DateTimeKind.Utc;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TopResourceConsumers()
|
||||
{
|
||||
QueryStoreService service = GetMock();
|
||||
|
||||
MockRequest<QueryStoreQueryResult> request = new();
|
||||
await service.HandleGetTopResourceConsumersSummaryReportRequest(new GetTopResourceConsumersReportParams()
|
||||
{
|
||||
ConnectionOwnerUri = TestConnectionOwnerUri,
|
||||
ReturnAllQueries = true,
|
||||
SelectedMetric = Metric.WaitTime,
|
||||
SelectedStatistic = Statistic.Stdev,
|
||||
OrderByColumnId = "query_id",
|
||||
Descending = true,
|
||||
MinNumberOfQueryPlans = 1,
|
||||
TopQueriesReturned = 50,
|
||||
TimeInterval = TestTimeInterval
|
||||
}, request.Object);
|
||||
|
||||
request.AssertSuccess(nameof(service.HandleGetTopResourceConsumersSummaryReportRequest));
|
||||
Assert.AreEqual(QueryStoreBaselines.HandleGetTopResourceConsumersSummaryReportRequest.ReplaceLineEndings(), request.Result.Query.ReplaceLineEndings());
|
||||
|
||||
request = new();
|
||||
await service.HandleGetTopResourceConsumersDetailedSummaryReportRequest(new GetTopResourceConsumersReportParams()
|
||||
{
|
||||
ConnectionOwnerUri = TestConnectionOwnerUri,
|
||||
ReturnAllQueries = true,
|
||||
SelectedMetric = Metric.WaitTime,
|
||||
SelectedStatistic = Statistic.Stdev,
|
||||
OrderByColumnId = "query_id",
|
||||
Descending = true,
|
||||
MinNumberOfQueryPlans = 1,
|
||||
TopQueriesReturned = 50,
|
||||
TimeInterval = TestTimeInterval
|
||||
}, request.Object);
|
||||
|
||||
request.AssertSuccess(nameof(service.HandleGetTopResourceConsumersDetailedSummaryReportRequest));
|
||||
Assert.AreEqual(QueryStoreBaselines.HandleGetTopResourceConsumersDetailedSummaryReportRequest.ReplaceLineEndings(), request.Result.Query.ReplaceLineEndings());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ForcedPlanQueries()
|
||||
{
|
||||
QueryStoreService service = GetMock();
|
||||
|
||||
MockRequest<QueryStoreQueryResult> request = new();
|
||||
await service.HandleGetForcedPlanQueriesReportRequest(new GetForcedPlanQueriesReportParams()
|
||||
{
|
||||
ConnectionOwnerUri = TestConnectionOwnerUri,
|
||||
ReturnAllQueries = true,
|
||||
SelectedMetric = Metric.WaitTime,
|
||||
SelectedStatistic = Statistic.Stdev,
|
||||
OrderByColumnId = "query_id",
|
||||
Descending = true,
|
||||
MinNumberOfQueryPlans = 1,
|
||||
TopQueriesReturned = 50,
|
||||
TimeInterval = TestTimeInterval
|
||||
}, request.Object);
|
||||
|
||||
request.AssertSuccess(nameof(service.HandleGetForcedPlanQueriesReportRequest));
|
||||
Assert.AreEqual(QueryStoreBaselines.HandleGetForcedPlanQueriesReportRequest.ReplaceLineEndings(), request.Result.Query.ReplaceLineEndings());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TrackedQueries()
|
||||
{
|
||||
QueryStoreService service = GetMock();
|
||||
|
||||
MockRequest<QueryStoreQueryResult> request = new();
|
||||
await service.HandleGetTrackedQueriesReportRequest(new GetTrackedQueriesReportParams()
|
||||
{
|
||||
QuerySearchText = "test search text"
|
||||
}, request.Object);
|
||||
|
||||
request.AssertSuccess(nameof(service.HandleGetTrackedQueriesReportRequest));
|
||||
Assert.AreEqual(QueryStoreBaselines.HandleGetTrackedQueriesReportRequest.ReplaceLineEndings(), request.Result.Query.ReplaceLineEndings());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task HighVariationQueries()
|
||||
{
|
||||
QueryStoreService service = GetMock();
|
||||
|
||||
MockRequest<QueryStoreQueryResult> request = new();
|
||||
await service.HandleGetHighVariationQueriesSummaryReportRequest(new GetHighVariationQueriesReportParams()
|
||||
{
|
||||
ConnectionOwnerUri = TestConnectionOwnerUri,
|
||||
ReturnAllQueries = true,
|
||||
SelectedMetric = Metric.WaitTime,
|
||||
SelectedStatistic = Statistic.Stdev,
|
||||
OrderByColumnId = "query_id",
|
||||
Descending = true,
|
||||
MinNumberOfQueryPlans = 1,
|
||||
TopQueriesReturned = 50,
|
||||
TimeInterval = TestTimeInterval
|
||||
}, request.Object);
|
||||
|
||||
request.AssertSuccess(nameof(service.HandleGetHighVariationQueriesSummaryReportRequest));
|
||||
Assert.AreEqual(QueryStoreBaselines.HandleGetHighVariationQueriesSummaryReportRequest.ReplaceLineEndings(), request.Result.Query.ReplaceLineEndings());
|
||||
|
||||
request = new();
|
||||
await service.HandleGetHighVariationQueriesDetailedSummaryReportRequest(new GetHighVariationQueriesReportParams()
|
||||
{
|
||||
ConnectionOwnerUri = TestConnectionOwnerUri,
|
||||
ReturnAllQueries = true,
|
||||
SelectedMetric = Metric.WaitTime,
|
||||
SelectedStatistic = Statistic.Stdev,
|
||||
OrderByColumnId = "query_id",
|
||||
Descending = true,
|
||||
MinNumberOfQueryPlans = 1,
|
||||
TopQueriesReturned = 50,
|
||||
TimeInterval = TestTimeInterval
|
||||
}, request.Object);
|
||||
|
||||
request.AssertSuccess(nameof(service.HandleGetHighVariationQueriesDetailedSummaryReportRequest));
|
||||
Assert.AreEqual(QueryStoreBaselines.HandleGetHighVariationQueriesDetailedSummaryReportRequest.ReplaceLineEndings(), request.Result.Query.ReplaceLineEndings());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task OverallResourceConsumption()
|
||||
{
|
||||
QueryStoreService service = GetMock();
|
||||
|
||||
MockRequest<QueryStoreQueryResult> request = new();
|
||||
await service.HandleGetOverallResourceConsumptionReportRequest(new GetOverallResourceConsumptionReportParams()
|
||||
{
|
||||
ConnectionOwnerUri = TestConnectionOwnerUri,
|
||||
ReturnAllQueries = true,
|
||||
SelectedMetric = Metric.WaitTime,
|
||||
SelectedStatistic = Statistic.Stdev,
|
||||
MinNumberOfQueryPlans = 1,
|
||||
TopQueriesReturned = 50,
|
||||
SpecifiedTimeInterval = TestTimeInterval,
|
||||
SpecifiedBucketInterval = BucketInterval.Hour
|
||||
}, request.Object);
|
||||
|
||||
request.AssertSuccess(nameof(service.HandleGetOverallResourceConsumptionReportRequest));
|
||||
Assert.AreEqual(QueryStoreBaselines.HandleGetOverallResourceConsumptionReportRequest.ReplaceLineEndings(), request.Result.Query.ReplaceLineEndings());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task RegressedQueries()
|
||||
{
|
||||
QueryStoreService service = GetMock();
|
||||
|
||||
MockRequest<QueryStoreQueryResult> request = new();
|
||||
await service.HandleGetRegressedQueriesSummaryReportRequest(new GetRegressedQueriesReportParams()
|
||||
{
|
||||
ConnectionOwnerUri = TestConnectionOwnerUri,
|
||||
ReturnAllQueries = true,
|
||||
SelectedMetric = Metric.WaitTime,
|
||||
SelectedStatistic = Statistic.Stdev,
|
||||
MinNumberOfQueryPlans = 1,
|
||||
TopQueriesReturned = 50,
|
||||
MinExecutionCount = 1,
|
||||
TimeIntervalHistory = TestTimeInterval,
|
||||
TimeIntervalRecent = RecentTestTimeInterval
|
||||
}, request.Object);
|
||||
|
||||
request.AssertSuccess(nameof(service.HandleGetRegressedQueriesSummaryReportRequest));
|
||||
Assert.AreEqual(QueryStoreBaselines.HandleGetRegressedQueriesSummaryReportRequest.ReplaceLineEndings(), request.Result.Query.ReplaceLineEndings());
|
||||
|
||||
request = new();
|
||||
await service.HandleGetRegressedQueriesDetailedSummaryReportRequest(new GetRegressedQueriesReportParams()
|
||||
{
|
||||
ConnectionOwnerUri = TestConnectionOwnerUri,
|
||||
ReturnAllQueries = true,
|
||||
SelectedMetric = Metric.WaitTime,
|
||||
SelectedStatistic = Statistic.Stdev,
|
||||
MinNumberOfQueryPlans = 1,
|
||||
TopQueriesReturned = 50,
|
||||
MinExecutionCount = 1,
|
||||
TimeIntervalHistory = TestTimeInterval,
|
||||
TimeIntervalRecent = RecentTestTimeInterval
|
||||
}, request.Object);
|
||||
|
||||
request.AssertSuccess(nameof(service.HandleGetRegressedQueriesDetailedSummaryReportRequest));
|
||||
Assert.AreEqual(QueryStoreBaselines.HandleGetRegressedQueriesDetailedSummaryReportRequest.ReplaceLineEndings(), request.Result.Query.ReplaceLineEndings());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task PlanSummary()
|
||||
{
|
||||
QueryStoreService service = GetMock();
|
||||
|
||||
MockRequest<QueryStoreQueryResult> request = new();
|
||||
await service.HandleGetPlanSummaryChartViewRequest(new GetPlanSummaryParams()
|
||||
{
|
||||
ConnectionOwnerUri = TestConnectionOwnerUri,
|
||||
QueryId = 97,
|
||||
TimeInterval = TestTimeInterval,
|
||||
TimeIntervalMode = PlanTimeIntervalMode.SpecifiedRange,
|
||||
SelectedMetric = Metric.WaitTime,
|
||||
SelectedStatistic = Statistic.Stdev
|
||||
}, request.Object);
|
||||
|
||||
request.AssertSuccess(nameof(service.HandleGetPlanSummaryChartViewRequest));
|
||||
Assert.AreEqual(QueryStoreBaselines.HandleGetPlanSummaryChartViewRequest.ReplaceLineEndings(), request.Result.Query.ReplaceLineEndings());
|
||||
|
||||
request = new();
|
||||
await service.HandleGetPlanSummaryGridViewRequest(new GetPlanSummaryGridViewParams()
|
||||
{
|
||||
ConnectionOwnerUri = TestConnectionOwnerUri,
|
||||
QueryId = 97,
|
||||
TimeInterval = TestTimeInterval,
|
||||
TimeIntervalMode = PlanTimeIntervalMode.SpecifiedRange,
|
||||
SelectedMetric = Metric.WaitTime,
|
||||
SelectedStatistic = Statistic.Stdev,
|
||||
OrderByColumnId = "count_executions",
|
||||
Descending = true
|
||||
}, request.Object);
|
||||
|
||||
request.AssertSuccess(nameof(service.HandleGetPlanSummaryGridViewRequest));
|
||||
Assert.AreEqual(QueryStoreBaselines.HandleGetPlanSummaryGridViewRequest.ReplaceLineEndings(), request.Result.Query.ReplaceLineEndings());
|
||||
}
|
||||
|
||||
private QueryStoreService GetMock()
|
||||
{
|
||||
Mock<QueryStoreService> mock = new Mock<QueryStoreService>();
|
||||
mock.Setup(s => s.GetAvailableMetrics(It.IsAny<QueryStoreReportParams>()))
|
||||
.Returns(new List<Metric>()
|
||||
{
|
||||
Metric.ClrTime,
|
||||
Metric.CPUTime,
|
||||
Metric.Dop,
|
||||
Metric.Duration,
|
||||
Metric.ExecutionCount,
|
||||
Metric.LogicalReads,
|
||||
Metric.LogicalWrites,
|
||||
Metric.LogMemoryUsed,
|
||||
Metric.MemoryConsumption,
|
||||
Metric.PhysicalReads,
|
||||
Metric.RowCount,
|
||||
Metric.TempDbMemoryUsed,
|
||||
Metric.WaitTime
|
||||
});
|
||||
|
||||
return mock.Object;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user