Merge branch 'microsoft:master' into segovoni/samples/manage/polyBase/external-table

This commit is contained in:
Sergio Govoni
2024-05-23 15:28:56 +02:00
committed by GitHub
11 changed files with 6590 additions and 30 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|
+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."
}