From 25c18d33903f68d4bf9178333f1eeb4ee25382d4 Mon Sep 17 00:00:00 2001 From: Benjin Dubishar Date: Fri, 1 Sep 2023 10:56:39 -0700 Subject: [PATCH] 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 --- Packages.props | 1 + ....Management.QueryStoreModel.163.26.1.nupkg | Bin 0 -> 146338 bytes .../HostLoader.cs | 4 + .../Microsoft.SqlTools.ServiceLayer.csproj | 1 + .../QueryStore/BasicTimeInterval.cs | 55 ++ .../Contracts/GetForcedPlanQueriesReport.cs | 40 + .../GetHighVariationQueriesReport.cs | 49 + .../GetOverallResourceConsumptionReport.cs | 48 + .../Contracts/GetRegressedQueriesReport.cs | 62 ++ .../GetTopResourceConsumersReport.cs | 49 + .../Contracts/GetTrackedQueryReport.cs | 31 + .../Contracts/PlanSummaryReportParams.cs | 115 +++ .../Contracts/QueryStoreReportParams.cs | 123 +++ .../QueryStore/QueryStoreService.cs | 552 ++++++++++++ .../QueryStore/QueryStoreBaselines.cs | 849 ++++++++++++++++++ .../QueryStore/QueryStoreTests.cs | 284 ++++++ 16 files changed, 2263 insertions(+) create mode 100644 bin/nuget/Microsoft.SqlServer.Management.QueryStoreModel.163.26.1.nupkg create mode 100644 src/Microsoft.SqlTools.ServiceLayer/QueryStore/BasicTimeInterval.cs create mode 100644 src/Microsoft.SqlTools.ServiceLayer/QueryStore/Contracts/GetForcedPlanQueriesReport.cs create mode 100644 src/Microsoft.SqlTools.ServiceLayer/QueryStore/Contracts/GetHighVariationQueriesReport.cs create mode 100644 src/Microsoft.SqlTools.ServiceLayer/QueryStore/Contracts/GetOverallResourceConsumptionReport.cs create mode 100644 src/Microsoft.SqlTools.ServiceLayer/QueryStore/Contracts/GetRegressedQueriesReport.cs create mode 100644 src/Microsoft.SqlTools.ServiceLayer/QueryStore/Contracts/GetTopResourceConsumersReport.cs create mode 100644 src/Microsoft.SqlTools.ServiceLayer/QueryStore/Contracts/GetTrackedQueryReport.cs create mode 100644 src/Microsoft.SqlTools.ServiceLayer/QueryStore/Contracts/PlanSummaryReportParams.cs create mode 100644 src/Microsoft.SqlTools.ServiceLayer/QueryStore/Contracts/QueryStoreReportParams.cs create mode 100644 src/Microsoft.SqlTools.ServiceLayer/QueryStore/QueryStoreService.cs create mode 100644 test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/QueryStore/QueryStoreBaselines.cs create mode 100644 test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/QueryStore/QueryStoreTests.cs diff --git a/Packages.props b/Packages.props index cdd5afdb..61be96ad 100644 --- a/Packages.props +++ b/Packages.props @@ -29,6 +29,7 @@ + diff --git a/bin/nuget/Microsoft.SqlServer.Management.QueryStoreModel.163.26.1.nupkg b/bin/nuget/Microsoft.SqlServer.Management.QueryStoreModel.163.26.1.nupkg new file mode 100644 index 0000000000000000000000000000000000000000..ba61aa4023c4a8fa9e77643038db51402f9aaee3 GIT binary patch literal 146338 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

;)bKg~Dp4%zXon^I? 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

xv@n#wn;?Y2>Gv4e9|*7Citg$)O6Y&NN-KP(acf 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

&wZ&j{j5Qy=b5pR0~TaKJQQ!Kn~D50l0y>6PP3Y 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∈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 + diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryStore/BasicTimeInterval.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryStore/BasicTimeInterval.cs new file mode 100644 index 00000000..3eb77cd7 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryStore/BasicTimeInterval.cs @@ -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 +{ + ///

+ /// Represents a TimeInterval with strings for the start and end times instead of DateTimeOffsets for JRPC compatibility + /// + public class BasicTimeInterval + { + /// + /// Start time of this time interval, in ISO 8601 format (ToString("O")). + /// This property is ignored unless TimeIntervalOptions is set to Custom. + /// + public string StartDateTimeInUtc { get; set; } = null; + + /// + /// End time of this time interval, in ISO 8601 format (ToString("O")). + /// This property is ignored unless TimeIntervalOptions is set to Custom. + /// + public string EndDateTimeInUtc { get; set; } = null; + + /// + /// Time interval type. Unless set to Custom, then StartDateTimeInUtc and EndDateTimeInUtc are ignored. + /// + 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}'"); + } + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryStore/Contracts/GetForcedPlanQueriesReport.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryStore/Contracts/GetForcedPlanQueriesReport.cs new file mode 100644 index 00000000..f6a9e042 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryStore/Contracts/GetForcedPlanQueriesReport.cs @@ -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 +{ + /// + /// Parameters for getting a Forced Plan Queries report + /// + public class GetForcedPlanQueriesReportParams : OrderableQueryConfigurationParams + { + /// + /// Time interval for the report + /// + public BasicTimeInterval TimeInterval { get; set; } + + public override ForcedPlanQueriesConfiguration Convert() + { + ForcedPlanQueriesConfiguration config = base.Convert(); + config.TimeInterval = TimeInterval.Convert(); + + return config; + } + } + + /// + /// Gets the query for a Forced Plan Queries report + /// + public class GetForcedPlanQueriesReportRequest + { + public static readonly RequestType Type + = RequestType.Create("queryStore/getForcedPlanQueriesReport"); + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryStore/Contracts/GetHighVariationQueriesReport.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryStore/Contracts/GetHighVariationQueriesReport.cs new file mode 100644 index 00000000..bf274250 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryStore/Contracts/GetHighVariationQueriesReport.cs @@ -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 +{ + /// + /// Parameters for getting a High Variation Queries report + /// + public class GetHighVariationQueriesReportParams : OrderableQueryConfigurationParams + { + /// + /// Time interval for the report + /// + public BasicTimeInterval TimeInterval { get; set; } + + public override HighVariationConfiguration Convert() + { + HighVariationConfiguration config = base.Convert(); + config.TimeInterval = TimeInterval.Convert(); + + return config; + } + } + + /// + /// Gets the query for a High Variation Queries report + /// + public class GetHighVariationQueriesSummaryRequest + { + public static readonly RequestType Type + = RequestType.Create("queryStore/getHighVariationQueriesSummary"); + } + + /// + /// Gets the query for a detailed High Variation Queries report + /// + public class GetHighVariationQueriesDetailedSummaryRequest + { + public static readonly RequestType Type + = RequestType.Create("queryStore/getHighVariationQueriesDetailedSummary"); + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryStore/Contracts/GetOverallResourceConsumptionReport.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryStore/Contracts/GetOverallResourceConsumptionReport.cs new file mode 100644 index 00000000..8a2ae017 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryStore/Contracts/GetOverallResourceConsumptionReport.cs @@ -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 +{ + /// + /// Parameters for getting an Overall Resource Consumption report + /// + public class GetOverallResourceConsumptionReportParams : QueryConfigurationParams + { + /// + /// Time interval for the report + /// + public BasicTimeInterval SpecifiedTimeInterval { get; set; } + + /// + /// Bucket interval for the report + /// + public BucketInterval SpecifiedBucketInterval { get; set; } + + public override OverallResourceConsumptionConfiguration Convert() + { + OverallResourceConsumptionConfiguration result = base.Convert(); + + result.SpecifiedTimeInterval = SpecifiedTimeInterval.Convert(); + result.SelectedBucketInterval = SpecifiedBucketInterval; + + return result; + } + } + + /// + /// Gets the query for an Overall Resource Consumption report + /// + public class GetOverallResourceConsumptionReportRequest + { + public static readonly RequestType Type + = RequestType.Create("queryStore/getOverallResourceConsumptionReport"); + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryStore/Contracts/GetRegressedQueriesReport.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryStore/Contracts/GetRegressedQueriesReport.cs new file mode 100644 index 00000000..decb820f --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryStore/Contracts/GetRegressedQueriesReport.cs @@ -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 +{ + /// + /// Parameters for getting a Regressed Queries report + /// + public class GetRegressedQueriesReportParams : QueryConfigurationParams + { + /// + /// Time interval during which to look for performance regressions for the report + /// + public BasicTimeInterval TimeIntervalRecent { get; set; } + + /// + /// Time interval during which to establish baseline performance for the report + /// + public BasicTimeInterval TimeIntervalHistory { get; set; } + + /// + /// Minimum number of executions for a query to be included + /// + 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; + } + } + + /// + /// Gets the query for a Regressed Queries report + /// + public class GetRegressedQueriesSummaryRequest + { + public static readonly RequestType Type + = RequestType.Create("queryStore/getRegressedQueriesSummary"); + } + + /// + /// Gets the query for a detailed Regressed Queries report + /// + public class GetRegressedQueriesDetailedSummaryRequest + { + public static readonly RequestType Type + = RequestType.Create("queryStore/getRegressedQueriesDetailedSummary"); + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryStore/Contracts/GetTopResourceConsumersReport.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryStore/Contracts/GetTopResourceConsumersReport.cs new file mode 100644 index 00000000..dc8a32cc --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryStore/Contracts/GetTopResourceConsumersReport.cs @@ -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 +{ + /// + /// Parameters for getting a Top Resource Consumers report + /// + public class GetTopResourceConsumersReportParams : OrderableQueryConfigurationParams + { + /// + /// Time interval for the report + /// + public BasicTimeInterval TimeInterval { get; set; } + + public override TopResourceConsumersConfiguration Convert() + { + TopResourceConsumersConfiguration result = base.Convert(); + result.TimeInterval = TimeInterval.Convert(); + + return result; + } + } + + /// + /// Gets the query for a Top Resource Consumers report + /// + public class GetTopResourceConsumersSummaryRequest + { + public static readonly RequestType Type + = RequestType.Create("queryStore/getTopResourceConsumersSummary"); + } + + /// + /// Gets the query for a detailed Top Resource Consumers report + /// + public class GetTopResourceConsumersDetailedSummaryRequest + { + public static readonly RequestType Type + = RequestType.Create("queryStore/getTopResourceConsumersDetailedSummary"); + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryStore/Contracts/GetTrackedQueryReport.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryStore/Contracts/GetTrackedQueryReport.cs new file mode 100644 index 00000000..ce9d373f --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryStore/Contracts/GetTrackedQueryReport.cs @@ -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 +{ + /// + /// Parameters for getting a Tracked Queries report + /// + public class GetTrackedQueriesReportParams + { + /// + /// Search text for a query + /// + public string QuerySearchText { get; set; } + } + + /// + /// Gets the query for a Tracked Queries report + /// + public class GetTrackedQueriesReportRequest + { + public static readonly RequestType Type + = RequestType.Create("queryStore/getTrackedQueriesReport"); + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryStore/Contracts/PlanSummaryReportParams.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryStore/Contracts/PlanSummaryReportParams.cs new file mode 100644 index 00000000..02e3b76e --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryStore/Contracts/PlanSummaryReportParams.cs @@ -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 +{ + /// + /// Parameters for getting a Plan Summary + /// + public class GetPlanSummaryParams : TypedQueryStoreReportParams + { + /// + /// Query ID to view a summary of plans for + /// + public long QueryId { get; set; } + + /// + /// Mode of the time interval search + /// + public PlanTimeIntervalMode TimeIntervalMode { get; set; } + + /// + /// Time interval for the report + /// + public BasicTimeInterval TimeInterval { get; set; } + + /// + /// Metric to summarize + /// + public Metric SelectedMetric { get; set; } + + /// + /// Statistic to calculate on SelecticMetric + /// + public Statistic SelectedStatistic { get; set; } + + public override PlanSummaryConfiguration Convert() => new() + { + QueryId = QueryId, + TimeIntervalMode = TimeIntervalMode, + TimeInterval = TimeInterval.Convert(), + SelectedMetric = SelectedMetric, + SelectedStatistic = SelectedStatistic + }; + } + + /// + /// Parameters for getting the grid view of a Plan Summary + /// + public class GetPlanSummaryGridViewParams : GetPlanSummaryParams, IOrderableQueryParams + { + /// + /// Name of the column to order results by + /// + public string OrderByColumnId { get; set; } + + /// + /// Direction of the result ordering + /// + public bool Descending { get; set; } + + public string GetOrderByColumnId() => OrderByColumnId; + } + + /// + /// Parameters for getting the forced plan for a query + /// + public class GetForcedPlanParams : QueryStoreReportParams + { + /// + /// Query ID to view the plan for + /// + public long QueryId { get; set; } + + /// + /// Plan ID to view + /// + public long PlanId { get; set; } + } + + /// + /// Gets the query for a Plan Summary chart view + /// + public class GetPlanSummaryChartViewRequest + { + public static readonly RequestType Type + = RequestType.Create("queryStore/getPlanSummaryChartView"); + } + + /// + /// Gets the query for a Plan Summary grid view + /// + public class GetPlanSummaryGridViewRequest + { + public static readonly RequestType Type + = RequestType.Create("queryStore/getPlanSummaryGridView"); + } + + /// + /// Gets the query to view a forced plan + /// + public class GetForcedPlanRequest // there's also GetForcedPlanQueries (plural) in QSM; how is that not confusing... + { + public static readonly RequestType Type + = RequestType.Create("queryStore/getForcedPlan"); + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryStore/Contracts/QueryStoreReportParams.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryStore/Contracts/QueryStoreReportParams.cs new file mode 100644 index 00000000..2adcb500 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryStore/Contracts/QueryStoreReportParams.cs @@ -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 +{ + /// + /// Base class for a Query Store report parameters + /// + public abstract class QueryStoreReportParams + { + /// + /// Connection URI for the database + /// + public string ConnectionOwnerUri { get; set; } + } + + /// + /// Base class for Query Store report parameters that can be converted to a configuration object for use in QSM query generators + /// + /// + public abstract class TypedQueryStoreReportParams : QueryStoreReportParams + { + /// + /// Converts this SQL Tools Service parameter object to the QSM configuration object + /// + /// + public abstract T Convert(); + } + + /// + /// Base class for parameters for a report type that uses QueryConfigurationBase for its configuration + /// + /// + public abstract class QueryConfigurationParams : TypedQueryStoreReportParams where T : QueryConfigurationBase, new() + { + /// + /// Metric to summarize + /// + public Metric SelectedMetric { get; set; } + + /// + /// Statistic to calculate on SelecticMetric + /// + public Statistic SelectedStatistic { get; set; } + + /// + /// Number of queries to return if ReturnAllQueries is not set + /// + public int TopQueriesReturned { get; set; } + + /// + /// True to include all queries in the report; false to only include the top queries, up to the value specified by TopQueriesReturned + /// + public bool ReturnAllQueries { get; set; } + + /// + /// Minimum number of query plans for a query to included in the report + /// + public int MinNumberOfQueryPlans { get; set; } + + public override T Convert() => new T() + { + SelectedMetric = SelectedMetric, + SelectedStatistic = SelectedStatistic, + TopQueriesReturned = TopQueriesReturned, + ReturnAllQueries = ReturnAllQueries, + MinNumberOfQueryPlans = MinNumberOfQueryPlans + }; + } + + /// + /// Base class for parameters for a report that can be ordered by a specified column + /// + /// + public abstract class OrderableQueryConfigurationParams : QueryConfigurationParams, IOrderableQueryParams where T : QueryConfigurationBase, new() + { + /// + /// Name of the column to order results by + /// + public string OrderByColumnId { get; set; } + + /// + /// Direction of the result ordering + /// + public bool Descending { get; set; } + + /// + /// Gets the name of the column to order the report results by + /// + /// + public string GetOrderByColumnId() => OrderByColumnId; + } + + /// + /// Result containing a finalized query for a report + /// + public class QueryStoreQueryResult : ResultStatus + { + /// + /// Finalized query for a report + /// + public string Query { get; set; } + } + + /// + /// Interface for parameters for a report that can be ordered by a specific column + /// + public interface IOrderableQueryParams + { + /// + /// Gets the name of the column to order the report results by + /// + /// + string GetOrderByColumnId(); + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryStore/QueryStoreService.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryStore/QueryStoreService.cs new file mode 100644 index 00000000..db167da4 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryStore/QueryStoreService.cs @@ -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 +{ + /// + /// Main class for Query Store service + /// + public class QueryStoreService : BaseService + { + private static readonly Lazy instance = new Lazy(() => new QueryStoreService()); + + /// + /// Gets the singleton instance object + /// + public static QueryStoreService Instance => instance.Value; + + /// + /// Instance of the connection service, used to get the connection info for a given owner URI + /// + private ConnectionService ConnectionService { get; } + + public QueryStoreService() + { + ConnectionService = ConnectionService.Instance; + } + + /// + /// Initializes the service instance + /// + /// + 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 requestContext) + { + await RunWithErrorHandling(() => + { + TopResourceConsumersConfiguration config = requestParams.Convert(); + TopResourceConsumersQueryGenerator.TopResourceConsumersSummary(config, out IList columns); + ColumnInfo orderByColumn = GetOrderByColumn(requestParams, columns); + + string query = TopResourceConsumersQueryGenerator.TopResourceConsumersSummary(config, orderByColumn, requestParams.Descending, out _); + + Dictionary 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 requestContext) + { + await RunWithErrorHandling(() => + { + TopResourceConsumersConfiguration config = requestParams.Convert(); + TopResourceConsumersQueryGenerator.TopResourceConsumersDetailedSummary(GetAvailableMetrics(requestParams), config, out IList columns); + ColumnInfo orderByColumn = GetOrderByColumn(requestParams, columns); + + string query = TopResourceConsumersQueryGenerator.TopResourceConsumersDetailedSummary(GetAvailableMetrics(requestParams), config, orderByColumn, requestParams.Descending, out _); + + Dictionary 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 requestContext) + { + await RunWithErrorHandling(() => + { + ForcedPlanQueriesConfiguration config = requestParams.Convert(); + ForcedPlanQueriesQueryGenerator.ForcedPlanQueriesSummary(config, out IList columns); + ColumnInfo orderByColumn = GetOrderByColumn(requestParams, columns); + + string query = ForcedPlanQueriesQueryGenerator.ForcedPlanQueriesSummary(config, orderByColumn, requestParams.Descending, out IList _); + + 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 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 requestContext) + { + await RunWithErrorHandling(() => + { + HighVariationConfiguration config = requestParams.Convert(); + HighVariationQueryGenerator.HighVariationSummary(config, out IList columns); + ColumnInfo orderByColumn = GetOrderByColumn(requestParams, columns); + + string query = HighVariationQueryGenerator.HighVariationSummary(config, orderByColumn, requestParams.Descending, out _); + + Dictionary 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 requestContext) + { + await RunWithErrorHandling(() => + { + HighVariationConfiguration config = requestParams.Convert(); + IList availableMetrics = GetAvailableMetrics(requestParams); + HighVariationQueryGenerator.HighVariationDetailedSummary(availableMetrics, config, out IList columns); + ColumnInfo orderByColumn = GetOrderByColumn(requestParams, columns); + + string query = HighVariationQueryGenerator.HighVariationDetailedSummary(availableMetrics, config, orderByColumn, requestParams.Descending, out _); + + Dictionary 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 requestContext) + { + await RunWithErrorHandling(() => + { + OverallResourceConsumptionConfiguration config = requestParams.Convert(); + string query = OverallResourceConsumptionQueryGenerator.GenerateQuery(GetAvailableMetrics(requestParams), config, out _); + + Dictionary 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 requestContext) + { + await RunWithErrorHandling(() => + { + RegressedQueriesConfiguration config = requestParams.Convert(); + string query = RegressedQueriesQueryGenerator.RegressedQuerySummary(config, out _); + + Dictionary 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 requestContext) + { + await RunWithErrorHandling(() => + { + RegressedQueriesConfiguration config = requestParams.Convert(); + string query = RegressedQueriesQueryGenerator.RegressedQueryDetailedSummary(GetAvailableMetrics(requestParams), config, out _); + + Dictionary 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 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 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 requestContext) + { + await RunWithErrorHandling(() => + { + PlanSummaryConfiguration config = requestParams.Convert(); + + PlanSummaryQueryGenerator.PlanSummaryGridView(config, out IList columns); + ColumnInfo orderByColumn = GetOrderByColumn(requestParams, columns); + + string query = PlanSummaryQueryGenerator.PlanSummaryGridView(config, orderByColumn, requestParams.Descending, out _); + + Dictionary 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 requestContext) + { + await RunWithErrorHandling(() => + { + string query = PlanSummaryQueryGenerator.GetForcedPlanQuery(); + Dictionary 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 columnInfoList) + { + return requestParams.GetOrderByColumnId() != null ? columnInfoList.First(col => col.GetQueryColumnLabel() == requestParams.GetOrderByColumnId()) : columnInfoList[0]; + } + + internal virtual IList 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}'"); + } + } + + /// + /// Prepends declarations and definitions of to + /// + /// + /// + /// + private static string PrependSqlParameters(string query, Dictionary 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(); + } + + /// + /// 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 + /// + /// + /// data type and value portions of a parameter declaration, in the form "INT = 999" + 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 + } +} diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/QueryStore/QueryStoreBaselines.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/QueryStore/QueryStoreBaselines.cs new file mode 100644 index 00000000..8e7a1394 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/QueryStore/QueryStoreBaselines.cs @@ -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"; + } +} diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/QueryStore/QueryStoreTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/QueryStore/QueryStoreTests.cs new file mode 100644 index 00000000..a7627190 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/QueryStore/QueryStoreTests.cs @@ -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 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 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 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 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 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 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 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 mock = new Mock(); + mock.Setup(s => s.GetAvailableMetrics(It.IsAny())) + .Returns(new List() + { + 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; + } + } +}