mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-02-16 18:47:57 -05:00
[SQL Migration] SKU recommendation fixes + improvements (#1602)
* Fix null logger resulting in undefined error being surfaced to user * WIP - implement elastic recommendation model * WIP - implement elastic recommendation model * Clean up * Remove unnecessary content files after updating NuGet version * Refactor * Clean up
This commit is contained in:
@@ -74,16 +74,61 @@ namespace Microsoft.SqlTools.ServiceLayer.Migration.Contracts
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public List<SkuRecommendationResult> SqlDbRecommendationResults { get; set; }
|
public List<SkuRecommendationResult> SqlDbRecommendationResults { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How long the SQL DB recommendations took to generate, in milliseconds. Equal to -1 if SQL DB recommendations are not applicable.
|
||||||
|
/// </summary>
|
||||||
|
public long SqlDbRecommendationDurationInMs { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// List of SQL MI recommendation results, if applicable
|
/// List of SQL MI recommendation results, if applicable
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<SkuRecommendationResult> SqlMiRecommendationResults { get; set; }
|
public List<SkuRecommendationResult> SqlMiRecommendationResults { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How long the SQL MI recommendations took to generate, in milliseconds. Equal to -1 if SQL MI recommendations are not applicable.
|
||||||
|
/// </summary>
|
||||||
|
public long SqlMiRecommendationDurationInMs { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// List of SQL VM recommendation results, if applicable
|
/// List of SQL VM recommendation results, if applicable
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<SkuRecommendationResult> SqlVmRecommendationResults { get; set; }
|
public List<SkuRecommendationResult> SqlVmRecommendationResults { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How long the SQL VM recommendations took to generate, in milliseconds. Equal to -1 if SQL VM recommendations are not applicable.
|
||||||
|
/// </summary>
|
||||||
|
public long SqlVmRecommendationDurationInMs { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List of SQL DB recommendation results generated by the elastic model, if applicable
|
||||||
|
/// </summary>
|
||||||
|
public List<SkuRecommendationResult> ElasticSqlDbRecommendationResults { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How long the SQL DB recommendations took to generate using the elastic model, in milliseconds. Equal to -1 if SQL DB elastic recommendations are not applicable.
|
||||||
|
/// </summary>
|
||||||
|
public long ElasticSqlDbRecommendationDurationInMs { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List of SQL MI recommendation results generated by the elastic model, if applicable
|
||||||
|
/// </summary>
|
||||||
|
public List<SkuRecommendationResult> ElasticSqlMiRecommendationResults { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How long the SQL MI recommendations took to generate using the elastic model, in milliseconds. Equal to -1 if SQL MI elastic recommendations are not applicable.
|
||||||
|
/// </summary>
|
||||||
|
public long ElasticSqlMiRecommendationDurationInMs { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List of SQL VM recommendation results generated by the elastic model, if applicable
|
||||||
|
/// </summary>
|
||||||
|
public List<SkuRecommendationResult> ElasticSqlVmRecommendationResults { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How long the SQL VM recommendations took to generate using the elastic model, in milliseconds. Equal to -1 if SQL VM elastic recommendations are not applicable.
|
||||||
|
/// </summary>
|
||||||
|
public long ElasticSqlVmRecommendationDurationInMs { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// SQL instance requirements, representing an aggregated view of the performance requirements of the source instance
|
/// SQL instance requirements, representing an aggregated view of the performance requirements of the source instance
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -93,6 +138,24 @@ namespace Microsoft.SqlTools.ServiceLayer.Migration.Contracts
|
|||||||
/// File paths where the recommendation reports were saved
|
/// File paths where the recommendation reports were saved
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<string> SkuRecommendationReportPaths { get; set; }
|
public List<string> SkuRecommendationReportPaths { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// File paths where the recommendation reports generated by the elastic model were saved
|
||||||
|
/// </summary>
|
||||||
|
public List<string> ElasticSkuRecommendationReportPaths { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper class containing recommendation results, durations, and report paths, which is recommendation model-agnostic
|
||||||
|
internal class RecommendationResultSet {
|
||||||
|
internal List<SkuRecommendationResult> sqlDbResults;
|
||||||
|
internal List<SkuRecommendationResult> sqlMiResults;
|
||||||
|
internal List<SkuRecommendationResult> sqlVmResults;
|
||||||
|
internal long sqlDbDurationInMs;
|
||||||
|
internal long sqlMiDurationInMs;
|
||||||
|
internal long sqlVmDurationInMs;
|
||||||
|
internal string sqlDbReportPath;
|
||||||
|
internal string sqlMiReportPath;
|
||||||
|
internal string sqlVmReportPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class GetSkuRecommendationsRequest
|
public class GetSkuRecommendationsRequest
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Data;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using Microsoft.SqlServer.DataCollection.Common;
|
using Microsoft.SqlServer.DataCollection.Common;
|
||||||
using Microsoft.SqlServer.Management.Assessment.Checks;
|
using Microsoft.SqlServer.Management.Assessment.Checks;
|
||||||
using Microsoft.SqlServer.Management.Assessment;
|
using Microsoft.SqlServer.Management.Assessment;
|
||||||
@@ -29,9 +33,10 @@ using Microsoft.SqlServer.Migration.SkuRecommendation.Contracts;
|
|||||||
using Microsoft.SqlServer.Migration.SkuRecommendation.Contracts.Models.Sku;
|
using Microsoft.SqlServer.Migration.SkuRecommendation.Contracts.Models.Sku;
|
||||||
using Microsoft.SqlServer.Migration.SkuRecommendation.Contracts.Models;
|
using Microsoft.SqlServer.Migration.SkuRecommendation.Contracts.Models;
|
||||||
using Microsoft.SqlServer.Migration.SkuRecommendation.Contracts.Exceptions;
|
using Microsoft.SqlServer.Migration.SkuRecommendation.Contracts.Exceptions;
|
||||||
using Newtonsoft.Json;
|
|
||||||
using System.Reflection;
|
|
||||||
using Microsoft.SqlServer.Migration.SkuRecommendation.Contracts.Models.Environment;
|
using Microsoft.SqlServer.Migration.SkuRecommendation.Contracts.Models.Environment;
|
||||||
|
using Microsoft.SqlServer.Migration.SkuRecommendation.ElasticStrategy;
|
||||||
|
using Microsoft.SqlServer.Migration.SkuRecommendation.ElasticStrategy.AzureSqlManagedInstance;
|
||||||
|
using Microsoft.SqlServer.Migration.SkuRecommendation.ElasticStrategy.AzureSqlDatabase;
|
||||||
using Microsoft.SqlServer.Migration.SkuRecommendation.Models;
|
using Microsoft.SqlServer.Migration.SkuRecommendation.Models;
|
||||||
using Microsoft.SqlServer.Migration.SkuRecommendation.Utils;
|
using Microsoft.SqlServer.Migration.SkuRecommendation.Utils;
|
||||||
|
|
||||||
@@ -259,7 +264,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Migration
|
|||||||
SqlAssessmentConfiguration.ReportsAndLogsRootFolderPath = Path.GetDirectoryName(Logger.LogFileFullPath);
|
SqlAssessmentConfiguration.ReportsAndLogsRootFolderPath = Path.GetDirectoryName(Logger.LogFileFullPath);
|
||||||
|
|
||||||
CsvRequirementsAggregator aggregator = new CsvRequirementsAggregator(parameters.DataFolder);
|
CsvRequirementsAggregator aggregator = new CsvRequirementsAggregator(parameters.DataFolder);
|
||||||
|
|
||||||
SqlInstanceRequirements req = aggregator.ComputeSqlInstanceRequirements(
|
SqlInstanceRequirements req = aggregator.ComputeSqlInstanceRequirements(
|
||||||
agentId: null,
|
agentId: null,
|
||||||
instanceId: parameters.TargetSqlInstance,
|
instanceId: parameters.TargetSqlInstance,
|
||||||
@@ -270,28 +274,66 @@ namespace Microsoft.SqlTools.ServiceLayer.Migration
|
|||||||
dbsToInclude: new HashSet<string>(parameters.DatabaseAllowList),
|
dbsToInclude: new HashSet<string>(parameters.DatabaseAllowList),
|
||||||
hostRequirements: new SqlServerHostRequirements() { NICCount = 1 });
|
hostRequirements: new SqlServerHostRequirements() { NICCount = 1 });
|
||||||
|
|
||||||
|
RecommendationResultSet baselineResults = GenerateBaselineRecommendations(req, parameters);
|
||||||
|
RecommendationResultSet elasticResults = GenerateElasticRecommendations(req, parameters);
|
||||||
|
|
||||||
|
GetSkuRecommendationsResult results = new GetSkuRecommendationsResult
|
||||||
|
{
|
||||||
|
SqlDbRecommendationResults = baselineResults.sqlDbResults,
|
||||||
|
SqlDbRecommendationDurationInMs = baselineResults.sqlDbDurationInMs,
|
||||||
|
SqlMiRecommendationResults = baselineResults.sqlMiResults,
|
||||||
|
SqlMiRecommendationDurationInMs = baselineResults.sqlMiDurationInMs,
|
||||||
|
SqlVmRecommendationResults = baselineResults.sqlVmResults,
|
||||||
|
SqlVmRecommendationDurationInMs = baselineResults.sqlVmDurationInMs,
|
||||||
|
ElasticSqlDbRecommendationResults = elasticResults.sqlDbResults,
|
||||||
|
ElasticSqlDbRecommendationDurationInMs = elasticResults.sqlDbDurationInMs,
|
||||||
|
ElasticSqlMiRecommendationResults = elasticResults.sqlMiResults,
|
||||||
|
ElasticSqlMiRecommendationDurationInMs = elasticResults.sqlMiDurationInMs,
|
||||||
|
ElasticSqlVmRecommendationResults = elasticResults.sqlVmResults,
|
||||||
|
ElasticSqlVmRecommendationDurationInMs = elasticResults.sqlVmDurationInMs,
|
||||||
|
InstanceRequirements = req,
|
||||||
|
SkuRecommendationReportPaths = new List<string> { baselineResults.sqlDbReportPath, baselineResults.sqlMiReportPath, baselineResults.sqlVmReportPath },
|
||||||
|
ElasticSkuRecommendationReportPaths = new List<string> { elasticResults.sqlDbReportPath, elasticResults.sqlMiReportPath, elasticResults.sqlVmReportPath },
|
||||||
|
};
|
||||||
|
|
||||||
|
await requestContext.SendResult(results);
|
||||||
|
}
|
||||||
|
catch (FailedToQueryCountersException e)
|
||||||
|
{
|
||||||
|
await requestContext.SendError($"Unable to read collected performance data from {parameters.DataFolder}. Please specify another folder or start data collection instead.");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
await requestContext.SendError(e.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal RecommendationResultSet GenerateBaselineRecommendations(SqlInstanceRequirements req, GetSkuRecommendationsParams parameters) {
|
||||||
|
RecommendationResultSet resultSet = new RecommendationResultSet();
|
||||||
|
|
||||||
SkuRecommendationServiceProvider provider = new SkuRecommendationServiceProvider(new AzureSqlSkuBillingServiceProvider());
|
SkuRecommendationServiceProvider provider = new SkuRecommendationServiceProvider(new AzureSqlSkuBillingServiceProvider());
|
||||||
List<string> skuRecommendationReportPaths = new List<string>();
|
AzurePreferences prefs = new AzurePreferences() {
|
||||||
|
EligibleSkuCategories = null, // eligible SKU list will be adjusted with each recommendation type
|
||||||
|
ScalingFactor = parameters.ScalingFactor / 100.0,
|
||||||
|
TargetEnvironment = TargetEnvironmentType.Production
|
||||||
|
};
|
||||||
|
|
||||||
// generate SQL DB recommendations, if applicable
|
// generate SQL DB recommendations, if applicable
|
||||||
List<SkuRecommendationResult> sqlDbResults = new List<SkuRecommendationResult>();
|
|
||||||
if (parameters.TargetPlatforms.Contains("AzureSqlDatabase"))
|
if (parameters.TargetPlatforms.Contains("AzureSqlDatabase"))
|
||||||
{
|
{
|
||||||
var prefs = new AzurePreferences()
|
Stopwatch sqlDbStopwatch = new Stopwatch();
|
||||||
{
|
sqlDbStopwatch.Start();
|
||||||
EligibleSkuCategories = GetEligibleSkuCategories("AzureSqlDatabase", parameters.IncludePreviewSkus),
|
|
||||||
ScalingFactor = parameters.ScalingFactor / 100.0
|
|
||||||
};
|
|
||||||
sqlDbResults = provider.GetSkuRecommendation(prefs, req);
|
|
||||||
|
|
||||||
if (sqlDbResults.Count < parameters.DatabaseAllowList.Count)
|
prefs.EligibleSkuCategories = GetEligibleSkuCategories("AzureSqlDatabase", parameters.IncludePreviewSkus);
|
||||||
|
resultSet.sqlDbResults = provider.GetSkuRecommendation(prefs, req);
|
||||||
|
|
||||||
|
if (resultSet.sqlDbResults.Count < parameters.DatabaseAllowList.Count)
|
||||||
{
|
{
|
||||||
// if there are fewer recommendations than expected, find which databases didn't have a result generated and create a result with a null SKU
|
// if there are fewer recommendations than expected, find which databases didn't have a result generated and create a result with a null SKU
|
||||||
// TO-DO: in the future the NuGet will supply this logic directly so this won't be necessary anymore
|
List<string> databasesWithRecommendation = resultSet.sqlDbResults.Select(db => db.DatabaseName).ToList();
|
||||||
List<string> databasesWithRecommendation = sqlDbResults.Select(db => db.DatabaseName).ToList();
|
|
||||||
foreach (var databaseWithoutRecommendation in parameters.DatabaseAllowList.Where(db => !databasesWithRecommendation.Contains(db)))
|
foreach (var databaseWithoutRecommendation in parameters.DatabaseAllowList.Where(db => !databasesWithRecommendation.Contains(db)))
|
||||||
{
|
{
|
||||||
sqlDbResults.Add(new SkuRecommendationResult()
|
resultSet.sqlDbResults.Add(new SkuRecommendationResult()
|
||||||
{
|
{
|
||||||
//SqlInstanceName = sqlDbResults.FirstOrDefault().SqlInstanceName,
|
//SqlInstanceName = sqlDbResults.FirstOrDefault().SqlInstanceName,
|
||||||
SqlInstanceName = parameters.TargetSqlInstance,
|
SqlInstanceName = parameters.TargetSqlInstance,
|
||||||
@@ -305,31 +347,31 @@ namespace Microsoft.SqlTools.ServiceLayer.Migration
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sqlDbStopwatch.Stop();
|
||||||
|
resultSet.sqlDbDurationInMs = sqlDbStopwatch.ElapsedMilliseconds;
|
||||||
|
|
||||||
SkuRecommendationReport sqlDbReport = new SkuRecommendationReport(
|
SkuRecommendationReport sqlDbReport = new SkuRecommendationReport(
|
||||||
new Dictionary<SqlInstanceRequirements, List<SkuRecommendationResult>> {{ req, sqlDbResults }},
|
new Dictionary<SqlInstanceRequirements, List<SkuRecommendationResult>> { { req, resultSet.sqlDbResults } },
|
||||||
AzureSqlTargetPlatform.AzureSqlDatabase.ToString());
|
AzureSqlTargetPlatform.AzureSqlDatabase.ToString());
|
||||||
var sqlDbRecommendationReportFileName = String.Format("SkuRecommendationReport-AzureSqlDatabase-{0}", DateTime.UtcNow.ToString("yyyyMMddHH-mmss", CultureInfo.InvariantCulture));
|
var sqlDbRecommendationReportFileName = String.Format("SkuRecommendationReport-AzureSqlDatabase-Baseline-{0}", DateTime.UtcNow.ToString("yyyyMMddHH-mmss", CultureInfo.InvariantCulture));
|
||||||
var sqlDbRecommendationReportFullPath = Path.Combine(SqlAssessmentConfiguration.ReportsAndLogsRootFolderPath, sqlDbRecommendationReportFileName);
|
var sqlDbRecommendationReportFullPath = Path.Combine(SqlAssessmentConfiguration.ReportsAndLogsRootFolderPath, sqlDbRecommendationReportFileName);
|
||||||
ExportRecommendationResultsAction.ExportRecommendationResults(sqlDbReport, SqlAssessmentConfiguration.ReportsAndLogsRootFolderPath, false, sqlDbRecommendationReportFileName);
|
ExportRecommendationResultsAction.ExportRecommendationResults(sqlDbReport, SqlAssessmentConfiguration.ReportsAndLogsRootFolderPath, false, sqlDbRecommendationReportFileName);
|
||||||
skuRecommendationReportPaths.Add(sqlDbRecommendationReportFullPath + ".html");
|
resultSet.sqlDbReportPath = sqlDbRecommendationReportFullPath + ".html";
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate SQL MI recommendations, if applicable
|
// generate SQL MI recommendations, if applicable
|
||||||
List<SkuRecommendationResult> sqlMiResults = new List<SkuRecommendationResult>();
|
|
||||||
if (parameters.TargetPlatforms.Contains("AzureSqlManagedInstance"))
|
if (parameters.TargetPlatforms.Contains("AzureSqlManagedInstance"))
|
||||||
{
|
{
|
||||||
var prefs = new AzurePreferences()
|
Stopwatch sqlMiStopwatch = new Stopwatch();
|
||||||
{
|
sqlMiStopwatch.Start();
|
||||||
EligibleSkuCategories = GetEligibleSkuCategories("AzureSqlManagedInstance", parameters.IncludePreviewSkus),
|
|
||||||
ScalingFactor = parameters.ScalingFactor / 100.0
|
prefs.EligibleSkuCategories = GetEligibleSkuCategories("AzureSqlManagedInstance", parameters.IncludePreviewSkus);
|
||||||
};
|
resultSet.sqlMiResults = provider.GetSkuRecommendation(prefs, req);
|
||||||
sqlMiResults = provider.GetSkuRecommendation(prefs, req);
|
|
||||||
|
|
||||||
// if no result was generated, create a result with a null SKU
|
// if no result was generated, create a result with a null SKU
|
||||||
// TO-DO: in the future the NuGet will supply this logic directly so this won't be necessary anymore
|
if (!resultSet.sqlMiResults.Any())
|
||||||
if (!sqlMiResults.Any())
|
|
||||||
{
|
{
|
||||||
sqlMiResults.Add(new SkuRecommendationResult()
|
resultSet.sqlMiResults.Add(new SkuRecommendationResult()
|
||||||
{
|
{
|
||||||
SqlInstanceName = parameters.TargetSqlInstance,
|
SqlInstanceName = parameters.TargetSqlInstance,
|
||||||
DatabaseName = null,
|
DatabaseName = null,
|
||||||
@@ -341,32 +383,31 @@ namespace Microsoft.SqlTools.ServiceLayer.Migration
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sqlMiStopwatch.Stop();
|
||||||
|
resultSet.sqlMiDurationInMs = sqlMiStopwatch.ElapsedMilliseconds;
|
||||||
|
|
||||||
SkuRecommendationReport sqlMiReport = new SkuRecommendationReport(
|
SkuRecommendationReport sqlMiReport = new SkuRecommendationReport(
|
||||||
new Dictionary<SqlInstanceRequirements, List<SkuRecommendationResult>> { { req, sqlMiResults } },
|
new Dictionary<SqlInstanceRequirements, List<SkuRecommendationResult>> { { req, resultSet.sqlMiResults } },
|
||||||
AzureSqlTargetPlatform.AzureSqlManagedInstance.ToString());
|
AzureSqlTargetPlatform.AzureSqlManagedInstance.ToString());
|
||||||
var sqlMiRecommendationReportFileName = String.Format("SkuRecommendationReport-AzureSqlManagedInstance-{0}", DateTime.UtcNow.ToString("yyyyMMddHH-mmss", CultureInfo.InvariantCulture));
|
var sqlMiRecommendationReportFileName = String.Format("SkuRecommendationReport-AzureSqlManagedInstance-Baseline-{0}", DateTime.UtcNow.ToString("yyyyMMddHH-mmss", CultureInfo.InvariantCulture));
|
||||||
var sqlMiRecommendationReportFullPath = Path.Combine(SqlAssessmentConfiguration.ReportsAndLogsRootFolderPath, sqlMiRecommendationReportFileName);
|
var sqlMiRecommendationReportFullPath = Path.Combine(SqlAssessmentConfiguration.ReportsAndLogsRootFolderPath, sqlMiRecommendationReportFileName);
|
||||||
ExportRecommendationResultsAction.ExportRecommendationResults(sqlMiReport, SqlAssessmentConfiguration.ReportsAndLogsRootFolderPath, false, sqlMiRecommendationReportFileName);
|
ExportRecommendationResultsAction.ExportRecommendationResults(sqlMiReport, SqlAssessmentConfiguration.ReportsAndLogsRootFolderPath, false, sqlMiRecommendationReportFileName);
|
||||||
skuRecommendationReportPaths.Add(sqlMiRecommendationReportFullPath + ".html");
|
resultSet.sqlMiReportPath = sqlMiRecommendationReportFullPath + ".html";
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate SQL VM recommendations, if applicable
|
// generate SQL VM recommendations, if applicable
|
||||||
List<SkuRecommendationResult> sqlVmResults = new List<SkuRecommendationResult>();
|
|
||||||
if (parameters.TargetPlatforms.Contains("AzureSqlVirtualMachine"))
|
if (parameters.TargetPlatforms.Contains("AzureSqlVirtualMachine"))
|
||||||
{
|
{
|
||||||
var prefs = new AzurePreferences()
|
Stopwatch sqlVmStopwatch = new Stopwatch();
|
||||||
{
|
sqlVmStopwatch.Start();
|
||||||
EligibleSkuCategories = GetEligibleSkuCategories("AzureSqlVirtualMachine", parameters.IncludePreviewSkus),
|
|
||||||
ScalingFactor = parameters.ScalingFactor / 100.0,
|
prefs.EligibleSkuCategories = GetEligibleSkuCategories("AzureSqlVirtualMachine", parameters.IncludePreviewSkus);
|
||||||
TargetEnvironment = TargetEnvironmentType.Production
|
resultSet.sqlVmResults = provider.GetSkuRecommendation(prefs, req);
|
||||||
};
|
|
||||||
sqlVmResults = provider.GetSkuRecommendation(prefs, req);
|
|
||||||
|
|
||||||
// if no result was generated, create a result with a null SKU
|
// if no result was generated, create a result with a null SKU
|
||||||
// TO-DO: in the future the NuGet will supply this logic directly so this won't be necessary anymore
|
if (!resultSet.sqlVmResults.Any())
|
||||||
if (!sqlVmResults.Any())
|
|
||||||
{
|
{
|
||||||
sqlVmResults.Add(new SkuRecommendationResult()
|
resultSet.sqlVmResults.Add(new SkuRecommendationResult()
|
||||||
{
|
{
|
||||||
SqlInstanceName = parameters.TargetSqlInstance,
|
SqlInstanceName = parameters.TargetSqlInstance,
|
||||||
DatabaseName = null,
|
DatabaseName = null,
|
||||||
@@ -378,30 +419,147 @@ namespace Microsoft.SqlTools.ServiceLayer.Migration
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sqlVmStopwatch.Stop();
|
||||||
|
resultSet.sqlVmDurationInMs = sqlVmStopwatch.ElapsedMilliseconds;
|
||||||
|
|
||||||
SkuRecommendationReport sqlVmReport = new SkuRecommendationReport(
|
SkuRecommendationReport sqlVmReport = new SkuRecommendationReport(
|
||||||
new Dictionary<SqlInstanceRequirements, List<SkuRecommendationResult>> { { req, sqlVmResults } },
|
new Dictionary<SqlInstanceRequirements, List<SkuRecommendationResult>> { { req, resultSet.sqlVmResults } },
|
||||||
AzureSqlTargetPlatform.AzureSqlVirtualMachine.ToString());
|
AzureSqlTargetPlatform.AzureSqlVirtualMachine.ToString());
|
||||||
var sqlVmRecommendationReportFileName = String.Format("SkuRecommendationReport-AzureSqlVirtualMachine-{0}", DateTime.UtcNow.ToString("yyyyMMddHH-mmss", CultureInfo.InvariantCulture));
|
var sqlVmRecommendationReportFileName = String.Format("SkuRecommendationReport-AzureSqlVirtualMachine-Baseline-{0}", DateTime.UtcNow.ToString("yyyyMMddHH-mmss", CultureInfo.InvariantCulture));
|
||||||
var sqlVmRecommendationReportFullPath = Path.Combine(SqlAssessmentConfiguration.ReportsAndLogsRootFolderPath, sqlVmRecommendationReportFileName);
|
var sqlVmRecommendationReportFullPath = Path.Combine(SqlAssessmentConfiguration.ReportsAndLogsRootFolderPath, sqlVmRecommendationReportFileName);
|
||||||
ExportRecommendationResultsAction.ExportRecommendationResults(sqlVmReport, SqlAssessmentConfiguration.ReportsAndLogsRootFolderPath, false, sqlVmRecommendationReportFileName);
|
ExportRecommendationResultsAction.ExportRecommendationResults(sqlVmReport, SqlAssessmentConfiguration.ReportsAndLogsRootFolderPath, false, sqlVmRecommendationReportFileName);
|
||||||
skuRecommendationReportPaths.Add(sqlVmRecommendationReportFullPath + ".html");
|
resultSet.sqlVmReportPath = sqlVmRecommendationReportFullPath + ".html";
|
||||||
}
|
}
|
||||||
|
|
||||||
GetSkuRecommendationsResult results = new GetSkuRecommendationsResult
|
return resultSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal RecommendationResultSet GenerateElasticRecommendations(SqlInstanceRequirements req, GetSkuRecommendationsParams parameters) {
|
||||||
|
RecommendationResultSet resultSet = new RecommendationResultSet();
|
||||||
|
|
||||||
|
CsvAggregatorForElasticStrategy elasticaggregator = new CsvAggregatorForElasticStrategy(
|
||||||
|
instanceId: parameters.TargetSqlInstance,
|
||||||
|
directory: parameters.DataFolder,
|
||||||
|
queryInterval: parameters.PerfQueryIntervalInSec,
|
||||||
|
logger: null,
|
||||||
|
dbsToInclude: new HashSet<string>(parameters.DatabaseAllowList));
|
||||||
|
|
||||||
|
// generate SQL DB recommendations, if applicable
|
||||||
|
if (parameters.TargetPlatforms.Contains("AzureSqlDatabase"))
|
||||||
{
|
{
|
||||||
SqlDbRecommendationResults = sqlDbResults,
|
Stopwatch sqlDbStopwatch = new Stopwatch();
|
||||||
SqlMiRecommendationResults = sqlMiResults,
|
sqlDbStopwatch.Start();
|
||||||
SqlVmRecommendationResults = sqlVmResults,
|
|
||||||
InstanceRequirements = req,
|
List<AzureSqlSkuCategory> eligibleSkuCategories = GetEligibleSkuCategories("AzureSqlDatabase", parameters.IncludePreviewSkus);
|
||||||
SkuRecommendationReportPaths = skuRecommendationReportPaths
|
ElasticStrategySKURecommendationPipeline pi = new ElasticStrategySKURecommendationPipeline(eligibleSkuCategories);
|
||||||
|
DataTable SqlMISpec = pi.SqlMISpec.Copy();
|
||||||
|
if (!parameters.IncludePreviewSkus)
|
||||||
|
{
|
||||||
|
SqlMISpec = pi.SqlMISpec.AsEnumerable().Where(
|
||||||
|
p => !p.Field<string>("SLO").Contains("Premium")).CopyToDataTable();
|
||||||
|
}
|
||||||
|
MISkuRecParams MiSkuRecParams = new MISkuRecParams(pi.SqlGPMIFileSpec, SqlMISpec, elasticaggregator.FileLevelTs, elasticaggregator.InstanceTs, pi.MILookupTable, Convert.ToDouble(parameters.ScalingFactor) / 100.0, parameters.TargetSqlInstance);
|
||||||
|
DbSkuRecParams DbSkuRecParams = new DbSkuRecParams(pi.SqlDbSpec, elasticaggregator.DatabaseTs, pi.DbLookupTable, Convert.ToDouble(parameters.ScalingFactor) / 100.0, parameters.TargetSqlInstance);
|
||||||
|
resultSet.sqlDbResults = pi.ElasticStrategyGetSkuRecommendation(MiSkuRecParams, DbSkuRecParams, req);
|
||||||
|
|
||||||
|
if (resultSet.sqlDbResults.Count < parameters.DatabaseAllowList.Count)
|
||||||
|
{
|
||||||
|
// if there are fewer recommendations than expected, find which databases didn't have a result generated and create a result with a null SKU
|
||||||
|
List<string> databasesWithRecommendation = resultSet.sqlDbResults.Select(db => db.DatabaseName).ToList();
|
||||||
|
foreach (var databaseWithoutRecommendation in parameters.DatabaseAllowList.Where(db => !databasesWithRecommendation.Contains(db)))
|
||||||
|
{
|
||||||
|
resultSet.sqlDbResults.Add(new SkuRecommendationResult()
|
||||||
|
{
|
||||||
|
//SqlInstanceName = sqlDbResults.FirstOrDefault().SqlInstanceName,
|
||||||
|
SqlInstanceName = parameters.TargetSqlInstance,
|
||||||
|
DatabaseName = databaseWithoutRecommendation,
|
||||||
|
TargetSku = null,
|
||||||
|
MonthlyCost = null,
|
||||||
|
Ranking = -1,
|
||||||
|
PositiveJustifications = null,
|
||||||
|
NegativeJustifications = null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlDbStopwatch.Stop();
|
||||||
|
resultSet.sqlDbDurationInMs = sqlDbStopwatch.ElapsedMilliseconds;
|
||||||
|
|
||||||
|
SkuRecommendationReport sqlDbReport = new SkuRecommendationReport(
|
||||||
|
new Dictionary<SqlInstanceRequirements, List<SkuRecommendationResult>> { { req, resultSet.sqlDbResults } },
|
||||||
|
AzureSqlTargetPlatform.AzureSqlDatabase.ToString());
|
||||||
|
var sqlDbRecommendationReportFileName = String.Format("SkuRecommendationReport-AzureSqlDatabase-Elastic-{0}", DateTime.UtcNow.ToString("yyyyMMddHH-mmss", CultureInfo.InvariantCulture));
|
||||||
|
var sqlDbRecommendationReportFullPath = Path.Combine(SqlAssessmentConfiguration.ReportsAndLogsRootFolderPath, sqlDbRecommendationReportFileName);
|
||||||
|
ExportRecommendationResultsAction.ExportRecommendationResults(sqlDbReport, SqlAssessmentConfiguration.ReportsAndLogsRootFolderPath, false, sqlDbRecommendationReportFileName);
|
||||||
|
resultSet.sqlDbReportPath = sqlDbRecommendationReportFullPath + ".html";
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate SQL MI recommendations, if applicable
|
||||||
|
if (parameters.TargetPlatforms.Contains("AzureSqlManagedInstance"))
|
||||||
|
{
|
||||||
|
Stopwatch sqlMiStopwatch = new Stopwatch();
|
||||||
|
sqlMiStopwatch.Start();
|
||||||
|
|
||||||
|
List<AzureSqlSkuCategory> eligibleSkuCategories = GetEligibleSkuCategories("AzureSqlManagedInstance", parameters.IncludePreviewSkus);
|
||||||
|
ElasticStrategySKURecommendationPipeline pi = new ElasticStrategySKURecommendationPipeline(eligibleSkuCategories);
|
||||||
|
DataTable SqlMISpec = pi.SqlMISpec.Copy();
|
||||||
|
if (!parameters.IncludePreviewSkus)
|
||||||
|
{
|
||||||
|
SqlMISpec = pi.SqlMISpec.AsEnumerable().Where(
|
||||||
|
p => !p.Field<string>("SLO").Contains("Premium")).CopyToDataTable();
|
||||||
|
}
|
||||||
|
MISkuRecParams MiSkuRecParams = new MISkuRecParams(pi.SqlGPMIFileSpec, SqlMISpec, elasticaggregator.FileLevelTs, elasticaggregator.InstanceTs, pi.MILookupTable, Convert.ToDouble(parameters.ScalingFactor) / 100.0, parameters.TargetSqlInstance);
|
||||||
|
DbSkuRecParams DbSkuRecParams = new DbSkuRecParams(pi.SqlDbSpec, elasticaggregator.DatabaseTs, pi.DbLookupTable, Convert.ToDouble(parameters.ScalingFactor) / 100.0, parameters.TargetSqlInstance);
|
||||||
|
resultSet.sqlMiResults = pi.ElasticStrategyGetSkuRecommendation(MiSkuRecParams, DbSkuRecParams, req);
|
||||||
|
|
||||||
|
// if no result was generated, create a result with a null SKU
|
||||||
|
if (!resultSet.sqlMiResults.Any())
|
||||||
|
{
|
||||||
|
resultSet.sqlMiResults.Add(new SkuRecommendationResult()
|
||||||
|
{
|
||||||
|
SqlInstanceName = parameters.TargetSqlInstance,
|
||||||
|
DatabaseName = null,
|
||||||
|
TargetSku = null,
|
||||||
|
MonthlyCost = null,
|
||||||
|
Ranking = -1,
|
||||||
|
PositiveJustifications = null,
|
||||||
|
NegativeJustifications = null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlMiStopwatch.Stop();
|
||||||
|
resultSet.sqlMiDurationInMs = sqlMiStopwatch.ElapsedMilliseconds;
|
||||||
|
|
||||||
|
SkuRecommendationReport sqlMiReport = new SkuRecommendationReport(
|
||||||
|
new Dictionary<SqlInstanceRequirements, List<SkuRecommendationResult>> { { req, resultSet.sqlMiResults } },
|
||||||
|
AzureSqlTargetPlatform.AzureSqlManagedInstance.ToString());
|
||||||
|
var sqlMiRecommendationReportFileName = String.Format("SkuRecommendationReport-AzureSqlManagedInstance-Elastic-{0}", DateTime.UtcNow.ToString("yyyyMMddHH-mmss", CultureInfo.InvariantCulture));
|
||||||
|
var sqlMiRecommendationReportFullPath = Path.Combine(SqlAssessmentConfiguration.ReportsAndLogsRootFolderPath, sqlMiRecommendationReportFileName);
|
||||||
|
ExportRecommendationResultsAction.ExportRecommendationResults(sqlMiReport, SqlAssessmentConfiguration.ReportsAndLogsRootFolderPath, false, sqlMiRecommendationReportFileName);
|
||||||
|
resultSet.sqlMiReportPath = sqlMiRecommendationReportFullPath + ".html";
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate SQL VM recommendations, if applicable
|
||||||
|
if (parameters.TargetPlatforms.Contains("AzureSqlVirtualMachine"))
|
||||||
|
{
|
||||||
|
// elastic model currently doesn't support VM recommendation, return null SKU for now
|
||||||
|
resultSet.sqlVmResults = new List<SkuRecommendationResult> {
|
||||||
|
new SkuRecommendationResult()
|
||||||
|
{
|
||||||
|
SqlInstanceName = parameters.TargetSqlInstance,
|
||||||
|
DatabaseName = null,
|
||||||
|
TargetSku = null,
|
||||||
|
MonthlyCost = null,
|
||||||
|
Ranking = -1,
|
||||||
|
PositiveJustifications = null,
|
||||||
|
NegativeJustifications = null,
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
resultSet.sqlVmDurationInMs = -1;
|
||||||
|
resultSet.sqlVmReportPath = String.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
await requestContext.SendResult(results);
|
return resultSet;
|
||||||
}
|
|
||||||
catch (FailedToQueryCountersException e)
|
|
||||||
{
|
|
||||||
await requestContext.SendError($"Unable to read collected performance data from {parameters.DataFolder}. Please specify another folder or start data collection instead.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class AssessmentRequest : IAssessmentRequest
|
internal class AssessmentRequest : IAssessmentRequest
|
||||||
@@ -442,6 +600,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Migration
|
|||||||
engine.SaveAssessmentResultsToJson(contextualizedAssessmentResult, false, assessmentReportFullPath);
|
engine.SaveAssessmentResultsToJson(contextualizedAssessmentResult, false, assessmentReportFullPath);
|
||||||
|
|
||||||
var server = (contextualizedAssessmentResult.Servers.Count > 0) ? ParseServerAssessmentInfo(contextualizedAssessmentResult.Servers[0], engine) : null;
|
var server = (contextualizedAssessmentResult.Servers.Count > 0) ? ParseServerAssessmentInfo(contextualizedAssessmentResult.Servers[0], engine) : null;
|
||||||
|
|
||||||
return new MigrationAssessmentResult()
|
return new MigrationAssessmentResult()
|
||||||
{
|
{
|
||||||
AssessmentResult = server,
|
AssessmentResult = server,
|
||||||
|
|||||||
@@ -60,12 +60,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Migration
|
|||||||
int perfQueryIntervalInSec,
|
int perfQueryIntervalInSec,
|
||||||
int numberOfIterations,
|
int numberOfIterations,
|
||||||
int staticQueryIntervalInSec,
|
int staticQueryIntervalInSec,
|
||||||
ISqlAssessmentLogger logger)
|
ISqlAssessmentLogger logger = null)
|
||||||
{
|
{
|
||||||
this.outputFolder = outputFolder;
|
this.outputFolder = outputFolder;
|
||||||
this.perfQueryIntervalInSec = perfQueryIntervalInSec;
|
this.perfQueryIntervalInSec = perfQueryIntervalInSec;
|
||||||
this.numberOfIterations = numberOfIterations;
|
this.numberOfIterations = numberOfIterations;
|
||||||
this._logger = logger;
|
this._logger = logger ?? new DefaultPerfDataCollectionLogger();
|
||||||
this.messages = new List<KeyValuePair<string, DateTime>>();
|
this.messages = new List<KeyValuePair<string, DateTime>>();
|
||||||
this.errors = new List<KeyValuePair<string, DateTime>>();
|
this.errors = new List<KeyValuePair<string, DateTime>>();
|
||||||
perfDataCache = new SqlPerfDataPointsCache(this.outputFolder, _logger);
|
perfDataCache = new SqlPerfDataPointsCache(this.outputFolder, _logger);
|
||||||
|
|||||||
@@ -65,9 +65,12 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Migration
|
|||||||
MigrationService service = new MigrationService();
|
MigrationService service = new MigrationService();
|
||||||
await service.HandleGetSkuRecommendationsRequest(requestParams, requestContext.Object);
|
await service.HandleGetSkuRecommendationsRequest(requestParams, requestContext.Object);
|
||||||
Assert.IsNotNull(result, "Get SKU Recommendation result is null");
|
Assert.IsNotNull(result, "Get SKU Recommendation result is null");
|
||||||
Assert.IsNotNull(result.SqlMiRecommendationResults, "Get MI SKU Recommendation result is null");
|
Assert.IsNotNull(result.SqlMiRecommendationResults, "Get MI SKU Recommendation baseline result is null");
|
||||||
|
Assert.IsNotNull(result.ElasticSqlMiRecommendationResults, "Get MI SKU Recommendation elastic result is null");
|
||||||
|
|
||||||
// TODO: Include Negative Justification in future when we start recommending more than one SKU.
|
// TODO: Include Negative Justification in future when we start recommending more than one SKU.
|
||||||
Assert.Greater(result.SqlMiRecommendationResults.First().PositiveJustifications.Count, 0, "No positive justification for MI SKU Recommendation result");
|
Assert.Greater(result.SqlMiRecommendationResults.First().PositiveJustifications.Count, 0, "No positive justification for MI SKU Recommendation result");
|
||||||
|
Assert.Greater(result.ElasticSqlMiRecommendationResults.First().PositiveJustifications.Count, 0, "No positive justification for MI SKU elastic Recommendation result");
|
||||||
|
|
||||||
Assert.IsNotNull(result.InstanceRequirements);
|
Assert.IsNotNull(result.InstanceRequirements);
|
||||||
Assert.AreEqual(result.InstanceRequirements.InstanceId, "TEST");
|
Assert.AreEqual(result.InstanceRequirements.InstanceId, "TEST");
|
||||||
|
|||||||
Reference in New Issue
Block a user