Merge branch 'segovoni/samples/manage/polyBase/external-table' of https://github.com/segovoni/sql-server-samples into segovoni/samples/manage/polyBase/external-table

This commit is contained in:
Sergio Govoni
2024-05-23 15:36:30 +02:00
12 changed files with 6607 additions and 43 deletions
File diff suppressed because it is too large Load Diff
@@ -4937,9 +4937,9 @@
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
},
"ejs": {
"version": "3.1.8",
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.8.tgz",
"integrity": "sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ==",
"version": "3.1.10",
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
"integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==",
"requires": {
"jake": "^10.8.5"
}
@@ -12762,9 +12762,9 @@
"integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ=="
},
"tar": {
"version": "6.1.15",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.1.15.tgz",
"integrity": "sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A==",
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
"integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==",
"requires": {
"chownr": "^2.0.0",
"fs-minipass": "^2.0.0",
@@ -731,7 +731,7 @@
},
"partHeader": {
"title": "SQL Server Instances by Location",
"subtitle": "SQL Servers instasnces by \"Location\" tag values"
"subtitle": "SQL Servers instances by \"Location\" tag values"
}
}
},
@@ -785,7 +785,7 @@
"content": {}
},
"partHeader": {
"title": "SQL Server Instasnces by Data Center",
"title": "SQL Server Instances by Data Center",
"subtitle": "SQL Servers instances by \"Datacenter\" tag values."
}
}
@@ -678,14 +678,14 @@
},
{
"name": "query",
"value": "resources\r\n | where type == \"microsoft.hybridcompute/machines\"\r\n | where properties.detectedProperties.mssqldiscovered == true\r\n | extend serverId = tolower(tostring(id))\r\n | extend serverName = name\r\n | extend serverProvisioningState = tostring(properties.provisioningState)\r\n | extend serverServerStatus = tostring(properties.status)\r\n | extend serverProcessorCount = properties.detectedProperties.processorCount\r\n | extend serverCoreCount = toint(properties.detectedProperties.coreCount)\r\n | extend serverLogicalCoreCount = toint(properties.detectedProperties.logicalCoreCount)\r\n | join kind= leftouter (\r\n resources\r\n | where type == \"microsoft.hybridcompute/machines/extensions\"\r\n | where properties.type in (\"WindowsAgent.SqlServer\",\"LinuxAgent.SqlServer\")\r\n | extend extensionId = tolower(tostring(id))\r\n | extend extensionServerId = tolower(tostring (iff(id contains \"/extensions/WindowsAgent.SqlServer\" or id contains \"/extensions/LinuxAgent.SqlServer\", substring(id, 0, indexof(id, \"/extensions/\")), \"\")))\r\n | extend extensionProvisioningState = tostring(properties.provisioningState) \r\n | extend extensionLicenseType = tostring(properties.settings.LicenseType)\r\n )\r\n on $left.serverId == $right.extensionServerId\r\n | join kind= leftouter (\r\n resources\r\n | where type == 'microsoft.azurearcdata/sqlserverinstances' \r\n | extend sqlId = tolower(tostring(id))\r\n | extend sqlServerId = tolower(tostring(properties.containerResourceId))\r\n | extend sqlName = name\r\n | extend sqlProvisioningState = tostring(properties.provisioningState)\r\n | extend sqlServerStatus = tostring(properties.status)\r\n | extend sqlVersion = tostring(properties.version)\r\n | extend sqlLicenseType = tostring(properties.licenseType)\r\n | extend sqlEdition = tostring(properties.edition)\r\n | extend sqlvcore = toint(properties.vCore)\r\n )\r\n on $left.extensionServerId == $right.sqlServerId\r\n | where isnotempty(sqlServerId)\r\n //| project sqlEdition, serverName, sqlName, sqlLicenseType, extensionLicenseType, serverCoreCount, sqlvcore\r\n | where sqlLicenseType in (\"Paid\", \"ServerCAL\")\r\n | where sqlEdition in (\"Enterprise\", \"Standard\")\r\n | summarize count(sqlName), min(serverCoreCount), sum(sqlvcore) by serverName, extensionLicenseType\r\n | summarize [\"Number of Servers\"] = count(serverName), [\"Number of SQL Servers\"] = sum(count_sqlName), \r\n [\"Number of Windows Cores\"] = sum(min_serverCoreCount), [\"Number of SQL Cores\"] = sum(sum_sqlvcore) \r\n by [\"Extension License Type\"] = extensionLicenseType",
"value": "resources\r\n | where type == \"microsoft.hybridcompute/machines\"\r\n | where properties.detectedProperties.mssqldiscovered == true\r\n | extend serverId = tolower(tostring(id))\r\n | extend serverName = name\r\n | extend serverProvisioningState = tostring(properties.provisioningState)\r\n | extend serverServerStatus = tostring(properties.status)\r\n | extend serverProcessorCount = properties.detectedProperties.processorCount\r\n | extend serverCoreCount = toint(properties.detectedProperties.coreCount)\r\n | extend serverLogicalCoreCount = toint(properties.detectedProperties.logicalCoreCount)\r\n | join kind= leftouter (\r\n resources\r\n | where type == \"microsoft.hybridcompute/machines/extensions\"\r\n | where properties.type in (\"WindowsAgent.SqlServer\",\"LinuxAgent.SqlServer\")\r\n | extend extensionId = tolower(tostring(id))\r\n | extend extensionServerId = tolower(tostring (iff(id contains \"/extensions/WindowsAgent.SqlServer\" or id contains \"/extensions/LinuxAgent.SqlServer\", substring(id, 0, indexof(id, \"/extensions/\")), \"\")))\r\n | extend extensionProvisioningState = tostring(properties.provisioningState) \r\n | extend extensionLicenseType = tostring(properties.settings.LicenseType)\r\n )\r\n on $left.serverId == $right.extensionServerId\r\n | join kind= leftouter (\r\n resources\r\n | where type == 'microsoft.azurearcdata/sqlserverinstances' \r\n | extend sqlId = tolower(tostring(id))\r\n | extend sqlServerId = tolower(tostring(properties.containerResourceId))\r\n | extend sqlName = name\r\n | extend sqlProvisioningState = tostring(properties.provisioningState)\r\n | extend sqlServerStatus = tostring(properties.status)\r\n | extend sqlVersion = tostring(properties.version)\r\n | extend sqlLicenseType = tostring(properties.licenseType)\r\n | extend sqlEdition = tostring(properties.edition)\r\n | extend sqlvcore = toint(properties.vCore)\r\n )\r\n on $left.extensionServerId == $right.sqlServerId\r\n | where isnotempty(sqlServerId)\r\n //| project sqlEdition, serverName, sqlName, sqlLicenseType, extensionLicenseType, serverCoreCount, sqlvcore\r\n | where sqlLicenseType in (\"Paid\", \"ServerCAL\")\r\n | where sqlEdition in (\"Enterprise\", \"Standard\")\r\n | summarize count(sqlName), min(serverCoreCount) by serverName, extensionLicenseType\r\n | summarize [\"Number of Servers\"] = count(serverName), [\"Number of SQL Servers\"] = sum(count_sqlName), \r\n [\"Number of Windows Cores\"] = sum(min_serverCoreCount) \r\n by [\"Extension License Type\"] = extensionLicenseType",
"isOptional": true
}
],
"type": "Extension/HubsExtension/PartType/ArgQueryGridTile",
"settings": {},
"partHeader": {
"title": "Number of Cores",
"title": "Number of Cores for Production Servers Only (EE/SE)",
"subtitle": "SQL License in (Paid and ServerCAL)"
}
}
@@ -1011,6 +1011,60 @@
"subtitle": "SQL Server 2014 ESU"
}
}
},
"19": {
"position": {
"x": 0,
"y": 38,
"colSpan": 18,
"rowSpan": 4
},
"metadata": {
"inputs": [
{
"name": "partTitle",
"value": "Query 2",
"isOptional": true
},
{
"name": "query",
"value": "resources\r\n | where type == \"microsoft.hybridcompute/machines\"\r\n | extend serverId = tolower(tostring(id))\r\n | extend serverName = name\r\n | extend serverSubscriptionId = subscriptionId\r\n | extend serverLocation = location\r\n | extend serverResourceGroup = resourceGroup\r\n | extend serverTags = tags\r\n | extend serverProvisioningState = tostring(properties.provisioningState)\r\n | extend serverServerStatus = tostring(properties.status)\r\n | extend serverDisplayName = tostring(properties.displayName)\r\n | extend serverMssqldiscovered = properties.detectedProperties.mssqldiscovered\r\n | extend serverComputerName = tostring(properties.osProfile.computerName)\r\n | project serverId, \r\n [\"Server Name\"] = serverName, [\"Display Name\"] = serverDisplayName, [\"Computer Name\"] = serverComputerName, \r\n [\"Server State\"] = serverProvisioningState, [\"Server Status\"] = serverServerStatus, [\"MSSQL Discovered\"] = serverMssqldiscovered,\r\n [\"Subscription Id\"] = serverSubscriptionId, [\"Location\"] = serverLocation, [\"Resource Group\"] = serverResourceGroup, [\"Server Tags\"] = serverTags\r\n | join kind= leftouter (\r\n resources\r\n | where type == \"microsoft.hybridcompute/machines/extensions\"\r\n | where properties.type in (\"WindowsAgent.SqlServer\",\"LinuxAgent.SqlServer\")\r\n | extend extensionId = tolower(tostring(id))\r\n | extend extensionServerId = tolower(tostring (iff(id contains \"/extensions/WindowsAgent.SqlServer\" or id contains \"/extensions/LinuxAgent.SqlServer\", \r\n substring(id, 0, indexof(id, \"/extensions/\")), \r\n \"\")))\r\n | extend extensionProvisioningState = tostring(properties.provisioningState) \r\n | extend extensionType = tostring(properties.type)\r\n | extend extensionVersion = properties.instanceView.typeHandlerVersion\r\n | parse properties with * 'SQL Server Extension Agent: ' sqlAgentStatus ';' *\r\n | parse properties with * 'uploadStatus : ' uploadStatus ';' *\r\n | extend extensionSQLAgentStatus = iff(isnotempty(sqlAgentStatus),sqlAgentStatus,\"Unhealthy\")\r\n | extend extensionUploadStatus = iff(isnotempty(uploadStatus),uploadStatus,\"Missing SQL Agent\")\r\n | extend extensionLicenseType = tostring(properties.settings.LicenseType)\r\n | project extensionId, extensionServerId,\r\n [\"Extension State\"] = extensionProvisioningState, [\"SQLExtension Status\"] = extensionSQLAgentStatus, [\"DPS Status\"] = extensionUploadStatus, \r\n [\"Extension Name\"] = extensionType, [\"Extension Version\"] = extensionVersion,\r\n [\"Extension License\"] = extensionLicenseType\r\n )\r\n on $left.serverId == $right.extensionServerId\r\n | summarize uploadStatusBreakdown = count() by [\"DPS Status\"]",
"isOptional": true
},
{
"name": "chartType",
"value": 1,
"isOptional": true
},
{
"name": "isShared",
"isOptional": true
},
{
"name": "queryId",
"value": "",
"isOptional": true
},
{
"name": "formatResults",
"isOptional": true
},
{
"name": "queryScope",
"value": {
"scope": 0,
"values": []
},
"isOptional": true
}
],
"type": "Extension/HubsExtension/PartType/ArgQueryChartTile",
"settings": {},
"partHeader": {
"title": "Data Processing Service (DPS) Status",
"subtitle": ""
}
}
}
}
}
@@ -741,6 +741,60 @@
"subtitle": ""
}
}
},
"14": {
"position": {
"x": 0,
"y": 30,
"colSpan": 19,
"rowSpan": 4
},
"metadata": {
"inputs": [
{
"name": "partTitle",
"value": "Query 1",
"isOptional": true
},
{
"name": "chartType",
"isOptional": true
},
{
"name": "isShared",
"isOptional": true
},
{
"name": "queryId",
"isOptional": true
},
{
"name": "formatResults",
"isOptional": true
},
{
"name": "queryScope",
"value": {
"scope": 0,
"values": []
},
"isOptional": true
},
{
"name": "query",
"value": "resources\r\n | where type =~ 'microsoft.hybridcompute/machines/extensions'\r\n | where properties.type in ('WindowsAgent.SqlServer','LinuxAgent.SqlServer')\r\n | parse id with * '/providers/Microsoft.HybridCompute/machines/' machineName '/extensions/' *\r\n | parse properties with * 'uploadStatus : ' uploadStatus ';' *\r\n | project uploadStatus, subscriptionId, resourceGroup, machineName\r\n | where uploadStatus !in ('OK') //comment this out to see all upload stats\r\n | order by uploadStatus desc",
"isOptional": true
}
],
"type": "Extension/HubsExtension/PartType/ArgQueryGridTile",
"settings": {
"content": {}
},
"partHeader": {
"title": "Servers with Errors communicating with Azure DPS Service",
"subtitle": "For more information see https://learn.microsoft.com/en-us/sql/sql-server/azure-arc/troubleshoot-telemetry-endpoint"
}
}
}
}
}
@@ -75,7 +75,7 @@ Private policies As Policy() = Nothing
Private srcSiteUrl As String = Nothing
Private snkSiteUrl As String = Nothing
Private logFilePath As String = "MigrationLog.txt"
Private logFilePath As String = "MigrationLog.csv"
''''''''''''''''''''''''''''''''''''''''''''''
Sub Main()
@@ -241,8 +241,8 @@ Sub Main()
RsSnk.CreateFolder(folderName, parentFolder, Nothing)
Catch er As Exception
If (er.Message.Contains("Microsoft.ReportingServices.Diagnostics.Utilities.ItemAlreadyExistsException"))
Console.ForegroundColor = ConsoleColor.Yellow
Console.WriteLine("Folder already exists.")
Console.ForegroundColor = ConsoleColor.Gray
Console.WriteLine("Folder already exists: " + folderName)
Console.ResetColor()
Else
Throw ' Unknown Exception
@@ -478,7 +478,7 @@ Function MigrateDataSource(srcItem As CatalogItem) As CatalogItem
Console.WriteLine(warningMsg)
Console.ResetColor()
LogErrorToFile("CATALOGITEM", "Warning", warningMsg)
LogErrorToFile("CATALOGITEM", "Warning: " + result.Path, warningMsg)
End If
@@ -567,7 +567,7 @@ Function MigrateCatalogItem(srcItem As CatalogItem) As CatalogItem
Console.WriteLine(warningMsg)
Console.ResetColor()
LogErrorToFile("CATALOGITEM", "Warning", warningMsg)
LogErrorToFile("CATALOGITEM", "Warning: " + result.TypeName, warningMsg)
End If
End If
Next
@@ -742,7 +742,7 @@ Sub MigrateReportSubscriptions(snkReport As CatalogItem, srcReport As CatalogIte
Console.Write(description)
If Not dataRetrievalPlan Is Nothing And TypeOf dataRetrievalPlan.Item Is DataSourceReference Then
Dim item As DataSourceReference = dataRetrievalPlan.Item
Dim ref As String = GetSnkPath(item.Reference)
Dim ref As String = GetSnkPathRef(item.Reference)
item.Reference = ref
End If
RsSnk.CreateDataDrivenSubscription(snkReport.Path, extensionSettings, dataRetrievalPlan, description, eventType, GetMatchData(matchData, RsSnk), parameters)
@@ -756,7 +756,7 @@ Sub MigrateReportSubscriptions(snkReport As CatalogItem, srcReport As CatalogIte
Console.WriteLine(warningMsg)
Console.ResetColor()
LogErrorToFile("SUBSCRIPTIONS", "Warning", warningMsg)
LogErrorToFile("SUBSCRIPTIONS", "Warning: " + snkReport.Path, warningMsg)
End If
End If
@@ -795,9 +795,9 @@ Sub MigrateReportSubscriptions(snkReport As CatalogItem, srcReport As CatalogIte
Console.WriteLine(warningMsg)
Console.ResetColor()
LogErrorToFile("SUBSCRIPTIONS", "Warning", warningMsg)
LogErrorToFile("SUBSCRIPTIONS", "Warning: " + snkReport.Path, warningMsg)
End If
End If
Next
Catch ex As Exception
@@ -990,10 +990,19 @@ End Sub
'Helper function to link snk reference with relative paths
Function GetSnkPathRef(srcPath As String) As String
Dim snkPath = srcPath
If srcSiteUrl isNot Nothing
snkPath = srcPath.Remove(0, srcSiteUrl.LastIndexOf("/"))
End If
Dim snkPath = srcPath
If Not SrcIsNative Then
'SharePoint integrated mode, will assume /sites server relative URL delimeter
If srcSiteUrl isNot Nothing
Dim delimeter = "/sites/"
snkPath = srcPath.Remove(0, srcSiteUrl.IndexOf(delimeter) + delimeter.Length-1)
End If
Else
'Treat path reference like other scenarios if not intergated mode
snkPath = GetSnkPath(srcPath)
End If
Return snkPath
End Function
@@ -1189,12 +1198,18 @@ Sub LogErrorToFile(source As String, errItem as String, message As String)
Dim writer as StreamWriter
Try
Dim fileExists As Boolean = Not File.Exists(logFilePath) OrElse New FileInfo(logFilePath).Length = 0
' Create a StreamWriter object
writer = New StreamWriter(logFilePath, True)
If fileExists Then
writer.WriteLine("DateTime, Source, ErrorItem, ErrorMessage")
End If
' Format log message with timestamp
Dim timeStamp As String = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
Dim formattedMessage as String = String.Format("{0}, {1}, {2}, ""{3}""", timeStamp, source, errItem, message.Replace(Environment.NewLine, ""))
Dim formattedMessage as String = String.Format("{0}, {1}, {2}, ""{3}""", timeStamp, source, errItem, message.Replace(Environment.NewLine, " "))
' Write the log entry to file
writer.WriteLine(formattedMessage)
@@ -11,7 +11,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.Identity" Version="1.10.4" />
<PackageReference Include="Azure.Identity" Version="1.11.0" />
<PackageReference Include="Azure.Security.KeyVault.Secrets" Version="4.5.0" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.1.4" />
<PackageReference Include="Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider" Version="3.0.0" />
@@ -15,8 +15,8 @@
<ItemGroup>
<PackageReference Include="Azure.Core" Version="1.35.0" />
<PackageReference Include="Azure.Identity" Version="1.10.2" />
<PackageReference Include="Azure.Core" Version="1.38.0" />
<PackageReference Include="Azure.Identity" Version="1.11.0" />
<PackageReference Include="Microsoft.Azure.Services.AppAuthentication" Version="1.6.1" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.1.3" />
<PackageReference Include="Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider" Version="3.0.0" />
@@ -43,7 +43,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Azure.Identity" Version="1.10.2" />
<PackageReference Include="Azure.Identity" Version="1.11.0" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="5.0.6" />
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="5.0.6" />
<PackageReference Include="Microsoft.Azure.Services.AppAuthentication" Version="1.6.1" />
@@ -30,7 +30,7 @@ The script accepts the following command line parameters:
|-SubId|subscription_id *or* a file_name|Optional: Subscription id or a .csv file with the list of subscriptions<sup>1</sup>. If not specified all subscriptions will be scanned|
|-ResourceGroup |resource_group_name|Optional: Limits the scope to a specific resource group|
|-MachineName |machine_name|Optional: Limits the scope to a specific machine|
|-LicenceType | "Paid", "PAYG" or "LicenseOnly"| Optional: Sets the license type to the specified value |
|-LicenseType | "Paid", "PAYG" or "LicenseOnly"| Optional: Sets the license type to the specified value |
|-EnableESU | "Yes", "No" | Optional. Enables the ESU policy the value is "Yes" or disables it if the value is "No". To enable, the license type must be "Paid" or "PAYG"|
|-Force| |Optional. Forces the change of the license type to the specified value on all installed extensions. If -Force is not specified, the -LicenseType value is set only if undefined. Ignored if -LicenseType is not specified|
@@ -1,15 +1,15 @@
<!-- Always leave the MS logo -->
![](https://github.com/microsoft/sql-server-samples/blob/master/media/solutions-microsoft-logo-small.png)
# Handling Update Statistics Error on External Tables in SQL Server
# Handling UPDATE STATISTICS error on SQL Server PolyBase external tables
This sample describes an option to update statistics on SQL Server PolyBase external tables!
This sample describes an option to update statistics on SQL Server PolyBase external tables.
## Background
In the process of configuring a maintenance plan for a SQL Server database with external tables and external data sources for PolyBase queries, an error occurred during the update statistics task.
## Problem Encountered
## Problem encountered
While attempting to update statistics on an external table, the following error message was encountered:
@@ -37,7 +37,7 @@ The object Update Statistics isn't supported on External Table
## About this sample
- **Applies to:** SQL Server 2017 (or higher)
- **Key features:** Update statistics
- **Key features:** UPDATE STATISTICS
- **Workload:** No workload related to this sample
- **Programming Language:** T-SQL
- **Authors:** [Sergio Govoni](https://www.linkedin.com/in/sgovoni/) | [Microsoft MVP Profile](https://mvp.microsoft.com/mvp/profile/c7b770c0-3c9a-e411-93f2-9cb65495d3c4) | [Blog](https://segovoni.medium.com/) | [GitHub](https://github.com/segovoni) | [Twitter](https://twitter.com/segovoni)
@@ -60,19 +60,19 @@ Upon investigation, it became apparent that SQL Server does not support the dire
## Resolution steps
1. Understanding the Limitation:
### Understanding the limitation
Referring to the documentation page, it explicitly states, "Updating statistics is not supported on external tables. To update statistics on an external table, drop and re-create the statistics."
2. Available Solutions:
### Available solutions
Option 1: Ignore External Tables:
This option is not feasible for maintenance plans managed by SQL Server Management Studio, necessitating third-party solutions.
* Option 1: Ignore External Tables:
* This option is not feasible for maintenance plans managed by SQL Server Management Studio, necessitating third-party solutions.
Option 2: Drop and Recreate Statistics:
Employ a stored procedure, `sp_drop_create_stats_external_table`, to generate T-SQL statements for dropping and creating statistics on external tables. This procedure supports statistics on multiple columns.
* Option 2: Drop and Recreate Statistics:
* Employ a stored procedure, `sp_drop_create_stats_external_table`, to generate T-SQL statements for dropping and creating statistics on external tables. This procedure supports statistics on multiple columns.
3. Implementation Guide:
### Implementation guide
- Execute `sp_drop_create_stats_external_table` to generate T-SQL statements
- Execute DROP STATISTICS statements before the maintenance statistics task
@@ -103,7 +103,11 @@ This code sample is provided for demonstration and educational purposes only. It
<a name=related-links></a>
## Related Links
## Related links
<!-- Links to more articles. Remember to delete "en-us" from the link path. -->
For more information, see these articles...
For more information, see these articles:
- [Data virtualization with PolyBase in SQL Server](https://learn.microsoft.com/sql/relational-databases/polybase/polybase-guide?WT.mc_id=DP-MVP-4029181)
- [UPDATE STATISTICS (Transact-SQL)](https://learn.microsoft.com/sql/t-sql/statements/update-statistics-transact-sql?WT.mc_id=DP-MVP-4029181)
- [External tables: Why create statistics?](https://learn.microsoft.com/answers/questions/978155/external-tables-why-create-statistics?WT.mc_id=DP-MVP-4029181)
+186
View File
@@ -0,0 +1,186 @@
# Enter parameters
$subscriptionId = Read-Host "<Subscription ID>"
$resourceGroup = Read-Host "<Resource Group>"
$vmName = Read-Host "<Virtual machine name>"
# Set resource details
$resourceType = "Microsoft.Compute/virtualMachines"
$resourceId = "/subscriptions/$subscriptionId/resourceGroups/$resourceGroup/providers/$resourceType/$vmName"
# Get Azure access token
$accessToken = az account get-access-token --query accessToken -o tsv
# Invoke Azure Monitor Metrics API
function Get-Metrics {
[CmdletBinding()]
param (
[string]$accessToken,
[string]$resourceId,
[string]$metricNames,
[string]$apiVersion = "2023-10-01"
)
try {
$startTime = (Get-Date).AddHours(-24).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ')
$endTime = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ')
$timespan = "$startTime/$endTime"
Write-Verbose "Evaluating timespan: $timespan"
$uri = "https://management.azure.com$resourceId/providers/Microsoft.Insights/metrics?api-version=$apiVersion&metricnames=$metricNames&aggregation=maximum&interval=PT1M&timespan=$timespan"
$headers = @{ "Authorization" = "Bearer $accessToken"; "Content-Type" = "application/json" }
$response = Invoke-RestMethod -Uri $uri -Headers $headers -Method Get
if ($response) {
Write-Verbose "API response successfully retrieved."
return $response
} else {
Write-Error "No response from API."
}
} catch {
Write-Error "Error retrieving metrics: $_"
}
}
# Check if data disk latency violates thresholds
function Check-Latency {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[Object]$metrics,
[Parameter()]
[int]$latencyThreshold = 500,
[Parameter()]
[int]$consecutiveCount = 5
)
$violationTimes = @()
foreach ($metric in $metrics.value) {
if ($metric.name.value -eq "Data Disk Latency") {
$count = 0
foreach ($dataPoint in $metric.timeseries[0].data) {
if ($dataPoint.maximum -gt $latencyThreshold) {
$count++
if ($count -ge $consecutiveCount) {
$violationTimes += $dataPoint.timeStamp
$count = 0 # Reset count after recording a violation
}
} else {
$count = 0 # Reset count if the sequence is broken
}
}
}
}
if ($violationTimes.Count -gt 0) {
Write-Verbose "Latency violations detected."
return @{ "Flag" = $true; "Times" = $violationTimes }
} else {
Write-Verbose "No latency violations detected."
return @{ "Flag" = $false }
}
}
# Check metrics other than latency to evaluate for throttling
function Check-OtherMetricsThrottled {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[Object]$metrics,
[Parameter()]
[int]$PercentageThreshold = 90,
[Parameter()]
[int]$consecutiveCount = 5
)
$violatedMetrics = @()
foreach ($metric in $metrics.value) {
$count = 0
foreach ($dataPoint in $metric.timeseries[0].data) {
if ($dataPoint.maximum -gt $PercentageThreshold) {
$count++
if ($count -ge $consecutiveCount) {
$violatedMetrics += @{ "Metric" = $metric.name.localizedValue; "Time" = $dataPoint.timeStamp; "Value" = $dataPoint.maximum }
break
}
} else {
$count = 0
}
}
}
if ($violatedMetrics.Count -gt 0) {
Write-Verbose "Other metrics violations detected."
} else {
Write-Verbose "No other metrics violations detected."
}
return $violatedMetrics
}
# Compare times for latency & other throttled metrics. Logs the volations with values & timestamps
function CompareTimes {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[Hashtable]$latencyResult,
[Parameter(Mandatory = $true)]
[Array]$otherMetrics
)
foreach ($metric in $otherMetrics) {
$otherDateTime = [DateTime]$metric["Time"]
$isWithinFiveMinutes = $false
$closestLatencyTime = $null
$closestTimeDifference = [int]::MaxValue
foreach ($latencyTime in $latencyResult.Times) {
$latencyDateTime = [DateTime]$latencyTime
$timeDifference = [Math]::Abs(($otherDateTime - $latencyDateTime).TotalMinutes)
if ($timeDifference -le 5) {
$isWithinFiveMinutes = $true
if ($timeDifference -lt $closestTimeDifference) {
$closestTimeDifference = $timeDifference
$closestLatencyTime = $latencyTime
}
}
}
if ($isWithinFiveMinutes) {
if ($otherDateTime -lt $closestLatencyTime) {
Write-Host "`n $($metric["Metric"]) limit was hit before latency spiked at $closestLatencyTime with value $($metric["Value"]). `n"
} else {
Write-Host "`n $($metric["Metric"]) hit its limit with value $($metric["Value"]) at $($metric["Time"])."
Write-Host "Latency spiked at $closestLatencyTime before $($metric["Metric"]) hit its limit `n"
}
} else {
Write-Host "`n Metric: $($metric["Metric"]) exceeded its threshold with a value of $($metric["Value"]) at $($metric["Time"]), but this was not within 5 minutes of any latency spikes."
}
}
}
# Prompt user for latency threshold
$latencyThreshold = Read-Host "Enter Latency Threshold (default is 500)"
if (-not [int]::TryParse($latencyThreshold, [ref]0)) {
$latencyThreshold = 500 # Use default if invalid input
Write-Host "No valid input provided. Using Default 500ms for disk latency threshold"
}
# Execute main logic
$latencyMetrics = Get-Metrics -accessToken $accessToken -resourceId $resourceId -metricNames "Data Disk Latency"
$latencyResult = Check-Latency -metrics $latencyMetrics -latencyThreshold $latencyThreshold
if ($latencyResult.Flag) {
# If latency is flagged, check for other metrics. If there is no disk latency, machine is likely not throttled but only at high consumption
Write-Verbose "Checking the following metrics: Data Disk Bandwidth Consumed Percentage,Data Disk IOPS Consumed Percentage,VM Cached Bandwidth Consumed Percentage,VM Cached IOPS Consumed Percentage,VM Uncached Bandwidth Consumed Percentage,VM Uncached IOPS Consumed Percentage"
$DiskVMMetrics = Get-Metrics -accessToken $accessToken -resourceId $resourceId -metricNames "Data Disk Bandwidth Consumed Percentage,Data Disk IOPS Consumed Percentage,VM Cached Bandwidth Consumed Percentage,VM Cached IOPS Consumed Percentage,VM Uncached Bandwidth Consumed Percentage,VM Uncached IOPS Consumed Percentage"
$additionalMetrics = Check-OtherMetricsThrottled -metrics $DiskVMMetrics
if ($additionalMetrics.Count -gt 0) {
CompareTimes $latencyResult $additionalMetrics
} else {
Write-Host "No metrics violations detected besides latency."
}
} else {
Write-Host "No latency issues detected."
}