{ "version": "Notebook/1.0", "items": [ { "type": 9, "content": { "version": "KqlParameterItem/1.0", "parameters": [ { "id": "sub-param-001", "version": "KqlParameterItem/1.0", "name": "Subscriptions", "type": 6, "multiSelect": true, "quote": "'", "delimiter": ",", "typeSettings": { "additionalResourceOptions": [ "value::all" ], "includeAll": true, "showDefault": false }, "defaultValue": "value::all", "value": [ "value::all" ] }, { "id": "node-trends-time-range", "version": "KqlParameterItem/1.0", "name": "NodeTrendsTimeRange", "label": "Historic Data Time Range", "type": 4, "isRequired": true, "typeSettings": { "selectableValues": [ { "durationMs": 604800000 }, { "durationMs": 1209600000 }, { "durationMs": 2592000000 }, { "durationMs": 5184000000 }, { "durationMs": 7776000000 }, { "durationMs": 15552000000 }, { "durationMs": 31536000000 } ], "allowCustom": true }, "value": { "durationMs": 2592000000 } }, { "id": "rg-filter-001", "version": "KqlParameterItem/1.0", "name": "ResourceGroupFilter", "type": 1, "description": "Optional: Filter by resource group name using wildcards. Example: *-prod-* or *hci*", "value": "", "label": "Resource Group Filter" }, { "id": "tag-filter-001", "version": "KqlParameterItem/1.0", "name": "ClusterTagName", "type": 2, "description": "Optional: Filter Azure Local clusters by tag name. Select from dropdown or type custom value.", "query": "resources\r\n| where type == \"microsoft.azurestackhci/clusters\"\r\n| project tags\r\n| mv-expand bagexpansion=array tags\r\n| extend tagName = tostring(tags[0])\r\n| where isnotempty(tagName)\r\n| distinct tagName\r\n| order by tagName asc", "crossComponentResources": [ "{Subscriptions}" ], "typeSettings": { "additionalResourceOptions": [], "showDefault": false }, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "value": null, "label": "Cluster Tag Name" }, { "id": "tag-value-001", "version": "KqlParameterItem/1.0", "name": "ClusterTagValue", "type": 2, "description": "Optional: Filter Azure Local clusters by tag value. Select from dropdown or type custom value.", "query": "resources\r\n| where type == \"microsoft.azurestackhci/clusters\"\r\n| where '{ClusterTagName}' != ''\r\n| extend tagValue = tostring(tags['{ClusterTagName}'])\r\n| where isnotempty(tagValue)\r\n| distinct tagValue\r\n| order by tagValue asc", "crossComponentResources": [ "{Subscriptions}" ], "typeSettings": { "additionalResourceOptions": [], "showDefault": false }, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "value": null, "label": "Cluster Tag Value" }, { "id": "time-range-001", "version": "KqlParameterItem/1.0", "name": "TimeRange", "type": 4, "isRequired": true, "typeSettings": { "selectableValues": [ { "durationMs": 86400000 }, { "durationMs": 259200000 }, { "durationMs": 604800000 }, { "durationMs": 1209600000 }, { "durationMs": 2592000000 }, { "durationMs": 3888000000 }, { "durationMs": 5184000000 } ], "allowCustom": true }, "value": { "durationMs": 3888000000 }, "label": "Time Range" }, { "id": "cluster-rg-map-param", "version": "KqlParameterItem/1.0", "name": "ClusterRGMap", "type": 1, "description": "Hidden: maps resource group to cluster name for HCI clusters", "query": "resources\r\n| where type == \"microsoft.azurestackhci/clusters\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n| project value = strcat(tolower(resourceGroup), ':', name)\r\n| summarize result = make_list(value)\r\n| project value = tostring(result)", "crossComponentResources": [ "{Subscriptions}" ], "isHiddenWhenLocked": true, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources" } ], "style": "pills", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces" }, "name": "global-subscription-param" }, { "type": 1, "content": { "json": "## πŸ–₯️ Azure Local LENS (Lifecycle, Events & Notification Status) Workbook\r\n\r\nThis workbook is a community-driven / open-source project, it is not a Microsoft-supported service offering. If you encounter any issues, have feedback or a new feature request, please [raise an Issue on GitHub](https://aka.ms/AzureLocalLENS/issues)." }, "name": "workbook-title-version" }, { "type": 1, "content": { "json": "
\r\nπŸ“‹ Workbook Version: v0.8.4
\r\nπŸ”„ Check GitHub for Updates β€” Compare your version against the latest release on GitHub, and copy/paste, then Apply to update if needed\r\n
", "style": "info" }, "name": "version-update-banner" }, { "type": 1, "content": { "json": "### ⚑ Quick Actions and Knowledge Links" }, "name": "quick-actions-header" }, { "type": 11, "content": { "version": "LinkItem/1.0", "style": "nav", "links": [ { "id": "supportability-forum-link", "cellValue": "https://github.com/Azure/AzureLocal-Supportability", "linkTarget": "Url", "linkLabel": "πŸ’¬ Azure Local Supportability Forum", "style": "link" }, { "id": "docs-link", "cellValue": "https://learn.microsoft.com/azure/azure-local/whats-new", "linkTarget": "Url", "linkLabel": "πŸ“š Azure Local Docs", "style": "link" }, { "id": "sbe-updates-link", "cellValue": "https://learn.microsoft.com/azure/azure-local/update/solution-builder-extension", "linkTarget": "Url", "linkLabel": "🧩 Solution Builder Extension (SBE) updates", "style": "link" } ] }, "name": "quick-actions-links" }, { "type": 11, "content": { "version": "LinkItem/1.0", "style": "nav", "links": [ { "id": "update-docs-link", "cellValue": "https://learn.microsoft.com/azure/azure-local/update/update-via-powershell-23h2", "linkTarget": "Url", "linkLabel": "πŸ”„ Update Guide", "style": "link" }, { "id": "activity-log-link", "cellValue": "https://portal.azure.com/#view/Microsoft_Azure_Monitoring/AzureMonitoringBrowseBlade/~/activityLog", "linkTarget": "Url", "linkLabel": "πŸ“œ Activity Log", "style": "link" }, { "id": "service-health-link", "cellValue": "https://portal.azure.com/#view/Microsoft_Azure_Health/AzureHealthBrowseBlade/~/serviceIssues", "linkTarget": "Url", "linkLabel": "πŸ₯ Azure Service Health", "style": "link" }, { "id": "alert-rules-link", "cellValue": "https://portal.azure.com/#view/Microsoft_Azure_Monitoring/AzureMonitoringBrowseBlade/~/alertsV2", "linkTarget": "Url", "linkLabel": "πŸ”” Create Alert Rule", "style": "link" } ] }, "name": "quick-actions-links-row2" }, { "type": 1, "content": { "json": "### Filter Instructions\r\n- **Subscriptions**: Select one or more subscriptions to filter data (defaults to all accessible subscriptions)\r\n- **Resource Group Filter**: Optional wildcard filter for resource group names. Use `*` as wildcard (e.g., `*-prod-*` matches 'rg-hci-prod-01')\r\n- **Cluster Tag**: Optional tag filter. Enter both Tag Name and Tag Value to filter by Azure Local cluster tags. This filter also applies to AKS Arc clusters and VMs in the same resource group as matching clusters.\r\n- **πŸ’‘ Tip**: Set **Auto refresh** to 30 minutes using the toolbar dropdown (⟳) for hands-free monitoring\r\n\r\n---" }, "name": "filter-instructions" }, { "type": 11, "content": { "version": "LinkItem/1.0", "style": "tabs", "tabStyle": "larger", "links": [ { "id": "summary-dashboard-tab", "cellValue": "selectedTab", "linkTarget": "parameter", "linkLabel": "πŸ“Š Azure Local Instances", "subTarget": "0", "preText": "", "style": "link" }, { "id": "capacity-tab-001", "cellValue": "selectedTab", "linkTarget": "parameter", "linkLabel": "πŸ—οΈ Capacity", "subTarget": "7", "style": "link" }, { "id": "a73ee4cb-fbb6-4d62-a290-18479bfe7958", "cellValue": "selectedTab", "linkTarget": "parameter", "linkLabel": "πŸ“‹ System Health", "subTarget": "3", "style": "link" }, { "id": "d9e5f4a2-0c3f-5678-9012-bcdef2345678", "cellValue": "selectedTab", "linkTarget": "parameter", "linkLabel": "πŸ”„ Update Progress", "subTarget": "5", "style": "link" }, { "id": "3f29b7be-5489-43f2-beb7-c90c618ad71c", "cellValue": "selectedTab", "linkTarget": "parameter", "linkLabel": "πŸ”— ARB Status", "subTarget": "1", "style": "link" }, { "id": "a285a540-ba34-4588-b467-b0b8b21d1494", "cellValue": "selectedTab", "linkTarget": "parameter", "linkLabel": "πŸ—„οΈ Azure Local Machines", "subTarget": "2", "style": "link" }, { "id": "vm-tab-001", "cellValue": "selectedTab", "linkTarget": "parameter", "linkLabel": "πŸ’» Azure Local VMs", "subTarget": "6", "style": "link" }, { "id": "c8d4f3a1-9b2e-4567-8901-abcdef123456", "cellValue": "selectedTab", "linkTarget": "parameter", "linkLabel": "☸️ AKS Arc Clusters", "subTarget": "4", "style": "link" } ] }, "name": "main-tabs" }, { "type": 12, "content": { "version": "NotebookGroup/1.0", "groupType": "editable", "title": "Azure Local Instances", "items": [ { "type": 1, "content": { "json": "**Last Refreshed:** {TimeRange:label} | Click refresh on any tile to update data" }, "name": "last-refreshed-text" }, { "type": 1, "content": { "json": "## πŸ“Š Visual Summary" }, "name": "section-header-charts" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.azurestackhci/clusters\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n| extend Status = iff(properties.status == \"ConnectedRecently\", \"Connected\", \"Disconnected\")\r\n| summarize Count = count() by Status", "size": 3, "title": "Cluster Connectivity", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "piechart", "chartSettings": { "seriesLabelSettings": [] } }, "customWidth": "33", "name": "pie-cluster-connectivity", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "extensibilityresources\r\n| where type == \"microsoft.azurestackhci/clusters/updatesummaries\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| extend clusterName = tolower(tostring(split(id, '/')[8]))\r\n| extend clusterRG = tolower(resourceGroup)\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterName = tolower(name), clusterRG = tolower(resourceGroup)\r\n) on clusterName, clusterRG\r\n| extend HealthStatus = tostring(properties.healthState)\r\n| extend HealthStatus = case(\r\n HealthStatus == \"Success\", \"Healthy\",\r\n HealthStatus == \"Warning\", \"Warning\",\r\n HealthStatus == \"Failure\", \"Failed\",\r\n HealthStatus == \"InProgress\", \"In Progress\",\r\n \"Unknown\")\r\n| summarize Count = count() by HealthStatus", "size": 3, "title": "Cluster Health Status", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "piechart", "chartSettings": { "seriesLabelSettings": [] } }, "customWidth": "33", "name": "pie-cluster-health", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.resourceconnector/appliances\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterRG = resourceGroup\r\n) on $left.resourceGroup == $right.clusterRG\r\n| extend Status = tostring(properties.status)\r\n| extend Status = iff(Status == \"Running\", \"Online\", \"Offline\")\r\n| summarize Count = count() by Status", "size": 3, "title": "ARB Status", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "piechart", "chartSettings": { "seriesLabelSettings": [] } }, "customWidth": "33", "name": "pie-arb-status", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 1, "content": { "json": "πŸ“ *See connectivity table below*" }, "customWidth": "33", "name": "link-row-connectivity", "styleSettings": { "margin": "0px", "padding": "0px 0px 0px 50px" } }, { "type": 1, "content": { "json": "πŸ“‹ *See System Health tab*" }, "customWidth": "33", "name": "link-row-health", "styleSettings": { "margin": "0px", "padding": "0px 0px 0px 50px" } }, { "type": 1, "content": { "json": "πŸ”— *See ARB Status tab*" }, "customWidth": "33", "name": "link-row-arb", "styleSettings": { "margin": "0px", "padding": "0px 0px 0px 60px" } }, { "type": 1, "content": { "json": "---\r\n## πŸ–₯️ Azure Local Totals and Connectivity" }, "name": "section-header-connectivity" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.azurestackhci/clusters\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n| summarize TotalClusters = count()\r\n| extend Label = 'Total Clusters'", "size": 4, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "tiles", "tileSettings": { "titleContent": { "columnMatch": "Label", "formatter": 1 }, "leftContent": { "columnMatch": "TotalClusters", "formatter": 12, "formatOptions": { "palette": "blue" } }, "showBorder": true, "size": "auto" } }, "customWidth": "16", "name": "tile-total-clusters", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.azurestackhci/clusters\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n| where properties.status == \"ConnectedRecently\"\r\n| summarize ConnectedClusters = count()\r\n| extend Label = 'Connected Clusters'", "size": 4, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "tiles", "tileSettings": { "titleContent": { "columnMatch": "Label", "formatter": 1 }, "leftContent": { "columnMatch": "ConnectedClusters", "formatter": 12, "formatOptions": { "palette": "green" } }, "showBorder": true, "size": "auto" } }, "customWidth": "16", "name": "tile-connected-clusters", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.azurestackhci/clusters\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n| where properties.status != \"ConnectedRecently\"\r\n| summarize DisconnectedClusters = count()\r\n| extend Label = 'Disconnected Clusters'", "size": 4, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "tiles", "tileSettings": { "titleContent": { "columnMatch": "Label", "formatter": 1 }, "leftContent": { "columnMatch": "DisconnectedClusters", "formatter": 12, "formatOptions": { "palette": "gray" } }, "showBorder": true, "size": "auto" } }, "customWidth": "16", "name": "tile-disconnected-clusters", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.azurestackhci/clusters\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n| summarize Total = count(), Connected = countif(properties.status == \"ConnectedRecently\")\r\n| extend Percentage = round(todouble(Connected) / todouble(Total) * 100, 1)\r\n| extend Label = '% Connected'\r\n| project Label, Percentage", "size": 4, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "tiles", "tileSettings": { "titleContent": { "columnMatch": "Label", "formatter": 1 }, "leftContent": { "columnMatch": "Percentage", "formatter": 18, "formatOptions": { "thresholdsOptions": "colors", "thresholdsGrid": [ { "operator": ">=", "thresholdValue": "90", "representation": "green", "text": "{0}{1}%" }, { "operator": ">=", "thresholdValue": "70", "representation": "yellow", "text": "{0}{1}%" }, { "operator": "Default", "thresholdValue": null, "representation": "redBright", "text": "{0}{1}%" } ] } }, "showBorder": true, "size": "auto" } }, "customWidth": "16", "name": "tile-percent-connected", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.hybridcompute/machines\"\r\n| where properties.cloudMetadata.provider == \"AzSHCI\"\r\n| where kind != \"HCI\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterRG = resourceGroup\r\n) on $left.resourceGroup == $right.clusterRG\r\n| summarize TotalNodes = count()\r\n| extend Label = 'Total Azure Local Machines'", "size": 4, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "tiles", "tileSettings": { "titleContent": { "columnMatch": "Label", "formatter": 1 }, "leftContent": { "columnMatch": "TotalNodes", "formatter": 12, "formatOptions": { "palette": "magenta" } }, "showBorder": true, "size": "auto" } }, "customWidth": "16", "name": "tile-total-azure-local-nodes", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.resourceconnector/appliances\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterRG = resourceGroup\r\n) on $left.resourceGroup == $right.clusterRG\r\n| extend Status = tostring(properties.status)\r\n| extend isOffline = iff(Status != \"Running\", 1, 0)\r\n| summarize TotalARB = count(), OfflineARB = sum(isOffline)\r\n| extend OfflinePercent = iff(TotalARB == 0, 0.0, round(todouble(OfflineARB) / todouble(TotalARB) * 100, 1))\r\n| extend Label = 'ARB Offline'\r\n| project Label, OfflineARB, OfflinePercent", "size": 4, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "tiles", "tileSettings": { "titleContent": { "columnMatch": "Label", "formatter": 1 }, "leftContent": { "columnMatch": "OfflineARB", "formatter": 12, "formatOptions": { "palette": "redBright" } }, "secondaryContent": { "columnMatch": "OfflinePercent", "formatter": 18, "formatOptions": { "thresholdsOptions": "colors", "thresholdsGrid": [ { "operator": "==", "thresholdValue": "0", "representation": "green", "text": "{0}{1}%" }, { "operator": "<=", "thresholdValue": "10", "representation": "yellow", "text": "{0}{1}%" }, { "operator": "Default", "thresholdValue": null, "representation": "redBright", "text": "{0}{1}%" } ] } }, "showBorder": true, "size": "auto" } }, "customWidth": "16", "name": "tile-arb-offline", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 1, "content": { "json": "## 🩺 Azure Local Health and Patching" }, "name": "section-header-health" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "extensibilityresources\r\n| where type == \"microsoft.azurestackhci/clusters/updatesummaries\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where properties.healthState == \"Success\"\r\n| extend clusterName = tolower(tostring(split(id, '/')[8]))\r\n| extend clusterRG = tolower(resourceGroup)\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterName = tolower(name), clusterRG = tolower(resourceGroup)\r\n) on clusterName, clusterRG\r\n| summarize HealthyClusters = count()\r\n| extend Label = 'Healthy Clusters'", "size": 4, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "tiles", "tileSettings": { "titleContent": { "columnMatch": "Label", "formatter": 1 }, "leftContent": { "columnMatch": "HealthyClusters", "formatter": 12, "formatOptions": { "palette": "greenDark" } }, "showBorder": true, "size": "auto" } }, "customWidth": "16", "name": "tile-healthy-clusters", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "extensibilityresources\r\n| where type == \"microsoft.azurestackhci/clusters/updatesummaries\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where properties.healthState == \"Warning\"\r\n| extend clusterName = tolower(tostring(split(id, '/')[8]))\r\n| extend clusterRG = tolower(resourceGroup)\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterName = tolower(name), clusterRG = tolower(resourceGroup)\r\n) on clusterName, clusterRG\r\n| summarize Warnings = count()\r\n| extend Label = 'Health Warnings'", "size": 4, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "tiles", "tileSettings": { "titleContent": { "columnMatch": "Label", "formatter": 1 }, "leftContent": { "columnMatch": "Warnings", "formatter": 12, "formatOptions": { "palette": "yellow" } }, "showBorder": true, "size": "auto" } }, "customWidth": "16", "name": "tile-warnings", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "extensibilityresources\r\n| where type == \"microsoft.azurestackhci/clusters/updatesummaries\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where properties.healthState == \"Failure\"\r\n| extend clusterName = tolower(tostring(split(id, '/')[8]))\r\n| extend clusterRG = tolower(resourceGroup)\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterName = tolower(name), clusterRG = tolower(resourceGroup)\r\n) on clusterName, clusterRG\r\n| summarize FailedPrechecks = count()\r\n| extend Label = 'Critical Health Issues'", "size": 4, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "tiles", "tileSettings": { "titleContent": { "columnMatch": "Label", "formatter": 1 }, "leftContent": { "columnMatch": "FailedPrechecks", "formatter": 12, "formatOptions": { "palette": "red" } }, "showBorder": true, "size": "auto" } }, "customWidth": "16", "name": "tile-failed-prechecks", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "extensibilityresources\r\n| where type == \"microsoft.azurestackhci/clusters/updatesummaries\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where properties.healthState == \"InProgress\"\r\n| extend clusterName = tolower(tostring(split(id, '/')[8]))\r\n| extend clusterRG = tolower(resourceGroup)\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterName = tolower(name), clusterRG = tolower(resourceGroup)\r\n) on clusterName, clusterRG\r\n| summarize InProgressChecks = count()\r\n| extend Label = 'Health Check InProgress'", "size": 4, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "tiles", "tileSettings": { "titleContent": { "columnMatch": "Label", "formatter": 1 }, "leftContent": { "columnMatch": "InProgressChecks", "formatter": 12, "formatOptions": { "palette": "blue" } }, "showBorder": true, "size": "auto" } }, "customWidth": "16", "name": "tile-inprogress-health", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.hybridcompute/machines\"\r\n| where properties.cloudMetadata.provider == \"AzSHCI\"\r\n| where kind != \"HCI\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| project machineName = name, machineResourceGroup = resourceGroup\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterResourceGroup = resourceGroup\r\n) on $left.machineResourceGroup == $right.clusterResourceGroup\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.hybridcompute/machines/extensions\"\r\n | where properties.provisioningState == \"Failed\"\r\n | parse id with * \"/machines/\" mName \"/extensions/\" *\r\n | project mName, resourceGroup\r\n) on $left.machineName == $right.mName and $left.machineResourceGroup == $right.resourceGroup\r\n| summarize FailedExtensions = count()\r\n| extend Label = 'Failed Extensions'", "size": 4, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "tiles", "tileSettings": { "titleContent": { "columnMatch": "Label", "formatter": 1 }, "leftContent": { "columnMatch": "FailedExtensions", "formatter": 12, "formatOptions": { "palette": "redDark" } }, "showBorder": true, "size": "auto" } }, "customWidth": "16", "name": "tile-failed-extensions", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "extensibilityresources\r\n| where type == \"microsoft.azurestackhci/clusters/updatesummaries\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| extend clusterName = tolower(tostring(split(id, '/')[8]))\r\n| extend clusterRG = tolower(resourceGroup)\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterName = tolower(name), clusterRG = tolower(resourceGroup)\r\n) on clusterName, clusterRG\r\n| summarize Total = count(), Healthy = countif(properties.healthState == \"Success\")\r\n| extend Percentage = round(todouble(Healthy) / todouble(Total) * 100, 1)\r\n| extend Label = '% Healthy'\r\n| project Label, Percentage", "size": 4, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "tiles", "tileSettings": { "titleContent": { "columnMatch": "Label", "formatter": 1 }, "leftContent": { "columnMatch": "Percentage", "formatter": 18, "formatOptions": { "thresholdsOptions": "colors", "thresholdsGrid": [ { "operator": ">=", "thresholdValue": "90", "representation": "green", "text": "{0}{1}%" }, { "operator": ">=", "thresholdValue": "70", "representation": "yellow", "text": "{0}{1}%" }, { "operator": "Default", "thresholdValue": null, "representation": "redBright", "text": "{0}{1}%" } ] } }, "showBorder": true, "size": "auto" } }, "customWidth": "16", "name": "tile-percent-healthy", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 1, "content": { "json": "## πŸ’Ό Azure Local Workloads" }, "name": "section-header-workloads" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.hybridcompute/machines\"\r\n| where kind == \"HCI\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterRG = resourceGroup\r\n) on $left.resourceGroup == $right.clusterRG\r\n| summarize TotalVMs = count()\r\n| extend Label = 'Total Azure Local VMs'", "size": 4, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "tiles", "tileSettings": { "titleContent": { "columnMatch": "Label", "formatter": 1 }, "leftContent": { "columnMatch": "TotalVMs", "formatter": 12, "formatOptions": { "palette": "purple" } }, "showBorder": true, "size": "auto" } }, "customWidth": "16", "name": "tile-total-vms", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.kubernetes/connectedclusters\"\r\n| where properties.infrastructure == \"azure_stack_hci\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| extend aksResourceId = tolower(id)\r\n| join kind=leftouter (\r\n extensibilityresources\r\n | where type == \"microsoft.hybridcontainerservice/provisionedclusterinstances\"\r\n | extend customLocKey = tolower(trim_end(\"/\", tostring(extendedLocation.name)))\r\n | extend aksResourceId = tolower(substring(id, 0, indexof(id, \"/providers/Microsoft.HybridContainerService\")))\r\n | project aksResourceId, customLocKey\r\n) on aksResourceId\r\n| join kind=leftouter (\r\n resources\r\n | where type == \"microsoft.extendedlocation/customlocations\"\r\n | where tostring(properties.hostResourceId) contains \"Microsoft.ResourceConnector/appliances\"\r\n | extend arcBridgeRG = tolower(tostring(split(tostring(properties.hostResourceId), '/')[4]))\r\n | project customLocKey = tolower(trim_end(\"/\", id)), arcBridgeRG\r\n) on customLocKey\r\n| join kind=leftouter (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project azureLocalClusterName = name, clusterRG = tolower(resourceGroup)\r\n) on $left.arcBridgeRG == $right.clusterRG\r\n| where '{ClusterTagName}' == '' or isnotempty(azureLocalClusterName)\r\n| summarize TotalAKSArc = count()\r\n| extend Label = 'Total AKS Arc Clusters'", "size": 4, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "tiles", "tileSettings": { "titleContent": { "columnMatch": "Label", "formatter": 1 }, "leftContent": { "columnMatch": "TotalAKSArc", "formatter": 12, "formatOptions": { "palette": "turquoise" } }, "showBorder": true, "size": "auto" } }, "customWidth": "16", "name": "tile-total-aks-arc", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 1, "content": { "json": "## πŸ”„ Update Compliance" }, "name": "section-header-update-compliance" }, { "type": 1, "content": { "json": "_Azure Local updates follow a 6-month rolling support window. Version format: `xx.YYMM.x.x` where YYMM indicates release year/month._" }, "name": "update-compliance-info" }, { "type": 11, "content": { "version": "LinkItem/1.0", "style": "nav", "links": [ { "id": "lifecycle-cadence-link", "cellValue": "https://learn.microsoft.com/azure/azure-local/update/about-updates-23h2#lifecycle-cadence", "linkTarget": "Url", "linkLabel": "πŸ“š Knowledge: Azure Local Lifecycle cadence", "style": "link" }, { "id": "latest-releases-link", "cellValue": "https://learn.microsoft.com/azure/azure-local/release-information-23h2#about-azure-local-releases", "linkTarget": "Url", "linkLabel": "πŸ“‹ Documentation: Azure Local - Latest Releases", "style": "link" } ] }, "name": "update-compliance-links" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "extensibilityresources\r\n| where type == \"microsoft.azurestackhci/clusters/updatesummaries\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| extend clusterName = tolower(tostring(split(id, '/')[8]))\r\n| extend clusterRG = tolower(resourceGroup)\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterName = tolower(name), clusterRG = tolower(resourceGroup)\r\n) on clusterName, clusterRG\r\n| extend currentVersion = tostring(properties.currentVersion)\r\n| extend versionParts = split(currentVersion, '.')\r\n| extend yymm = tostring(versionParts[1])\r\n| extend versionYear = toint(substring(yymm, 0, 2))\r\n| extend versionMonth = toint(substring(yymm, 2, 2))\r\n| extend versionDate = make_datetime(2000 + versionYear, versionMonth, 1)\r\n| extend cutoffDate = datetime_add('month', -6, startofmonth(now()))\r\n| extend isSupported = versionDate >= cutoffDate\r\n| where isSupported == true\r\n| summarize Supported = count()\r\n| extend Label = 'Supported release'", "size": 4, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "tiles", "tileSettings": { "titleContent": { "columnMatch": "Label", "formatter": 18, "formatOptions": { "thresholdsOptions": "colors", "thresholdsGrid": [ { "operator": "Default", "thresholdValue": null, "representation": "green", "text": "{0}{1}" } ] }, "tooltipFormat": { "tooltip": "Clusters running a release within the 6-month support window" } }, "leftContent": { "columnMatch": "Supported", "formatter": 12, "formatOptions": { "palette": "green" } }, "showBorder": true, "size": "auto" } }, "customWidth": "16", "name": "tile-supported-version", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "extensibilityresources\r\n| where type == \"microsoft.azurestackhci/clusters/updatesummaries\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| extend clusterName = tolower(tostring(split(id, '/')[8]))\r\n| extend clusterRG = tolower(resourceGroup)\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterName = tolower(name), clusterRG = tolower(resourceGroup)\r\n) on clusterName, clusterRG\r\n| extend currentVersion = tostring(properties.currentVersion)\r\n| extend versionParts = split(currentVersion, '.')\r\n| extend yymm = tostring(versionParts[1])\r\n| extend versionYear = toint(substring(yymm, 0, 2))\r\n| extend versionMonth = toint(substring(yymm, 2, 2))\r\n| extend versionDate = make_datetime(2000 + versionYear, versionMonth, 1)\r\n| extend cutoffDate = datetime_add('month', -6, startofmonth(now()))\r\n| extend isSupported = versionDate >= cutoffDate\r\n| where isSupported == false\r\n| summarize Unsupported = count()\r\n| extend Label = 'Unsupported release'", "size": 4, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "tiles", "tileSettings": { "titleContent": { "columnMatch": "Label", "formatter": 18, "formatOptions": { "thresholdsOptions": "colors", "thresholdsGrid": [ { "operator": "Default", "thresholdValue": null, "representation": "redBright", "text": "{0}{1}" } ] }, "tooltipFormat": { "tooltip": "Clusters running an unsupported release older than 6 months" } }, "leftContent": { "columnMatch": "Unsupported", "formatter": 12, "formatOptions": { "palette": "redBright" } }, "showBorder": true, "size": "auto" } }, "customWidth": "16", "name": "tile-unsupported-version", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "extensibilityresources\r\n| where type == \"microsoft.azurestackhci/clusters/updatesummaries\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where properties.state == \"UpdateAvailable\"\r\n| extend clusterName = tolower(tostring(split(id, '/')[8]))\r\n| extend clusterRG = tolower(resourceGroup)\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterName = tolower(name), clusterRG = tolower(resourceGroup)\r\n) on clusterName, clusterRG\r\n| summarize UpdateAvailable = count()\r\n| extend Label = 'Updates Available'", "size": 4, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "tiles", "tileSettings": { "titleContent": { "columnMatch": "Label", "formatter": 1 }, "leftContent": { "columnMatch": "UpdateAvailable", "formatter": 12, "formatOptions": { "palette": "yellow" } }, "showBorder": true, "size": "auto" } }, "customWidth": "16", "name": "tile-update-available", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "extensibilityresources\r\n| where type == \"microsoft.azurestackhci/clusters/updatesummaries\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where properties.state == \"UpdateInProgress\"\r\n| extend clusterName = tolower(tostring(split(id, '/')[8]))\r\n| extend clusterRG = tolower(resourceGroup)\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterName = tolower(name), clusterRG = tolower(resourceGroup)\r\n) on clusterName, clusterRG\r\n| summarize InProgress = count()\r\n| extend Label = 'Updates In Progress'", "size": 4, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "tiles", "tileSettings": { "titleContent": { "columnMatch": "Label", "formatter": 1 }, "leftContent": { "columnMatch": "InProgress", "formatter": 12, "formatOptions": { "palette": "blue" } }, "showBorder": true, "size": "auto" } }, "customWidth": "16", "name": "tile-update-in-progress", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "extensibilityresources\r\n| where type == \"microsoft.azurestackhci/clusters/updatesummaries\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where properties.state == \"UpdateFailed\"\r\n| extend clusterName = tolower(tostring(split(id, '/')[8]))\r\n| extend clusterRG = tolower(resourceGroup)\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterName = tolower(name), clusterRG = tolower(resourceGroup)\r\n) on clusterName, clusterRG\r\n| summarize Failed = count()\r\n| extend Label = 'Update Failed'", "size": 4, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "tiles", "tileSettings": { "titleContent": { "columnMatch": "Label", "formatter": 1 }, "leftContent": { "columnMatch": "Failed", "formatter": 12, "formatOptions": { "palette": "redBright" } }, "showBorder": true, "size": "auto" } }, "customWidth": "16", "name": "tile-update-failed", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "extensibilityresources\r\n| where type == \"microsoft.azurestackhci/clusters/updatesummaries\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| extend clusterName = tolower(tostring(split(id, '/')[8]))\r\n| extend clusterRG = tolower(resourceGroup)\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterName = tolower(name), clusterRG = tolower(resourceGroup)\r\n) on clusterName, clusterRG\r\n| extend solutionVersion = tostring(properties.currentVersion)\r\n| summarize Count = count() by solutionVersion\r\n| order by solutionVersion desc", "size": 1, "title": "Solution Version Distribution", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "barchart", "chartSettings": { "xAxis": "solutionVersion", "yAxis": [ "Count" ], "showLegend": true } }, "customWidth": "100", "name": "chart-solution-version-distribution", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 1, "content": { "json": "---\r\n## πŸ“ˆ Azure Local Clusters Deployment Over Time" }, "name": "text-cluster-deployments" }, { "type": 9, "content": { "version": "KqlParameterItem/1.0", "parameters": [ { "id": "cluster-deployment-months", "version": "KqlParameterItem/1.0", "name": "ClusterDeploymentMonths", "label": "Time Range", "type": 2, "isRequired": true, "value": "12", "typeSettings": { "additionalResourceOptions": [], "showDefault": false }, "jsonData": "[{\"value\":\"24\",\"label\":\"24 months\"},{\"value\":\"18\",\"label\":\"18 months\"},{\"value\":\"12\",\"label\":\"12 months\"},{\"value\":\"9\",\"label\":\"9 months\"},{\"value\":\"6\",\"label\":\"6 months\"},{\"value\":\"3\",\"label\":\"3 months\"},{\"value\":\"1\",\"label\":\"1 month\"},{\"value\":\"0.5\",\"label\":\"2 weeks\"},{\"value\":\"0.25\",\"label\":\"1 week\"}]" } ], "style": "pills", "queryType": 0, "resourceType": "microsoft.resourcegraph/resources" }, "name": "cluster-deployment-params" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.azurestackhci/clusters\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n| extend registrationDate = todatetime(properties.registrationTimestamp)\r\n| where isnotempty(registrationDate)\r\n| extend _days = todouble('{ClusterDeploymentMonths}') * 30\r\n| where registrationDate >= ago(_days * 1d)\r\n| extend TimeBucket = case(\r\n _days <= 30, startofday(registrationDate),\r\n _days <= 90, startofweek(registrationDate),\r\n startofmonth(registrationDate)\r\n)\r\n| summarize Count = count() by TimeBucket\r\n| extend Series = 'Clusters'\r\n| order by TimeBucket asc", "size": 0, "title": "Azure Local Clusters Registered Over Time", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "linechart", "chartSettings": { "xAxis": "TimeBucket", "yAxis": [ "Count" ], "group": "Series", "showLegend": true, "seriesLabelSettings": [] } }, "customWidth": "80", "name": "cluster-deployments-bar" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.azurestackhci/clusters\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n| extend registrationDate = todatetime(properties.registrationTimestamp)\r\n| where isnotempty(registrationDate)\r\n| where registrationDate >= ago(todouble('{ClusterDeploymentMonths}') * 30d)\r\n| extend deploymentMonth = format_datetime(registrationDate, 'yyyy-MM')\r\n| summarize ClusterCount = count() by deploymentMonth\r\n| extend sortOrder = 1\r\n| union (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | extend registrationDate = todatetime(properties.registrationTimestamp)\r\n | where isnotempty(registrationDate)\r\n | where registrationDate >= ago(todouble('{ClusterDeploymentMonths}') * 30d)\r\n | summarize ClusterCount = count()\r\n | extend deploymentMonth = 'TOTAL', sortOrder = 0\r\n)\r\n| order by sortOrder asc, deploymentMonth desc\r\n| project deploymentMonth, ClusterCount", "size": 1, "title": "Clusters Registered by Month", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "gridSettings": { "rowLimit": 2000, "labelSettings": [ { "columnId": "deploymentMonth", "label": "Month" }, { "columnId": "ClusterCount", "label": "Clusters Registered" } ] } }, "customWidth": "20", "name": "cluster-deployments-table" }, { "type": 1, "content": { "json": "### πŸ“Š All Azure Local Clusters" }, "name": "section-header-all-clusters" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.azurestackhci/clusters\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n| extend clusterName = tolower(name)\r\n| extend clusterRG = tolower(resourceGroup)\r\n| extend hciClusterId = tolower(id)\r\n| extend hciClusterRG = tolower(resourceGroup)\r\n| join kind=leftouter (\r\n extensibilityresources\r\n | where type == \"microsoft.azurestackhci/clusters/updatesummaries\"\r\n | extend clusterName = tolower(tostring(split(id, '/')[8]))\r\n | extend clusterRG = tolower(resourceGroup)\r\n | extend solutionVersion = tostring(properties.currentVersion)\r\n | project clusterName, clusterRG, solutionVersion\r\n) on clusterName, clusterRG\r\n| extend status = iff(properties.status == \"ConnectedRecently\", \"Connected\", \"Disconnected\")\r\n| extend nodeCount = array_length(properties.reportedProperties.nodes)\r\n| extend totalCores = toint(properties.reportedProperties.nodes[0].coreCount) * nodeCount\r\n| extend totalMemoryGB = toint(properties.reportedProperties.nodes[0].memoryInGiB) * nodeCount\r\n| extend osVersion = tostring(properties.reportedProperties.nodes[0].osDisplayVersion)\r\n| extend hardwareClass = tostring(properties.reportedProperties.hardwareClass)\r\n| extend manufacturer = tostring(properties.reportedProperties.nodes[0].manufacturer)\r\n| extend model = tostring(properties.reportedProperties.nodes[0].model)\r\n| extend lastSync = todatetime(properties.lastSyncTimestamp)\r\n| extend registrationDate = todatetime(properties.registrationTimestamp)\r\n| extend azureHybridBenefit = iff(tostring(properties.softwareAssuranceProperties.softwareAssuranceStatus) =~ 'Enabled', 'Enabled', 'Disabled')\r\n| extend windowsServerSubscription = iff(tostring(properties.desiredProperties.windowsServerSubscription) =~ 'Enabled', 'Enabled', 'Disabled')\r\n| extend azureVerificationForVMs = iff(tostring(properties.reportedProperties.imdsAttestation) =~ 'Enabled', 'Enabled', 'Disabled')\r\n| extend clusterLink = strcat('https://portal.azure.com/#@/resource', id)\r\n| extend vmLink = strcat('https://portal.azure.com/#@/resource', id, '/virtualmachines')\r\n| extend aksArcLink = strcat('https://portal.azure.com/#@/resource', id, '/kubernetesClusters')\r\n| extend machinesLink = strcat('https://portal.azure.com/#@/resource', id, '/machines')\r\n| project ClusterName = name, clusterLink, vmLink, aksArcLink, machinesLink, hciClusterId, hciClusterRG, resourceGroup, status, solutionVersion, nodeCount, totalCores, totalMemoryGB, osVersion, hardwareClass, manufacturer, model, lastSync, azureHybridBenefit, windowsServerSubscription, azureVerificationForVMs, location, registrationDate\r\n| order by status asc, ClusterName asc", "size": 0, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ] }, "conditionalVisibility": { "parameterName": "alwaysHidden", "comparison": "isEqualTo", "value": "true" }, "name": "all-clusters-base" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "extensibilityresources\r\n| where type == \"microsoft.hybridcontainerservice/provisionedclusterinstances\"\r\n| extend customLocId = tolower(tostring(extendedLocation.name))\r\n| summarize AKSArcCount = count() by customLocId\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.extendedlocation/customlocations\"\r\n | extend customLocId = tolower(id)\r\n | extend arcBridgeRG = tolower(tostring(split(tostring(properties.hostResourceId), '/')[4]))\r\n | project customLocId, arcBridgeRG\r\n) on customLocId\r\n| summarize AKSArcCount = sum(AKSArcCount) by arcBridgeRG", "size": 0, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ] }, "conditionalVisibility": { "parameterName": "alwaysHidden", "comparison": "isEqualTo", "value": "true" }, "name": "all-clusters-aksarc-count" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "extensibilityresources\r\n| where type == \"microsoft.azurestackhci/virtualmachineinstances\"\r\n| extend customLocId = tolower(tostring(extendedLocation.name))\r\n| summarize VMCount = count() by customLocId\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.extendedlocation/customlocations\"\r\n | extend customLocId = tolower(id)\r\n | extend arcBridgeRG = tolower(tostring(split(tostring(properties.hostResourceId), '/')[4]))\r\n | project customLocId, arcBridgeRG\r\n) on customLocId\r\n| summarize VMCount = sum(VMCount) by arcBridgeRG", "size": 0, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ] }, "conditionalVisibility": { "parameterName": "alwaysHidden", "comparison": "isEqualTo", "value": "true" }, "name": "all-clusters-vm-count" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "{\"version\":\"Merge/1.0\",\"merges\":[{\"id\":\"with-aksarc\",\"mergeType\":\"leftouter\",\"leftTable\":\"all-clusters-base\",\"rightTable\":\"all-clusters-aksarc-count\",\"leftColumn\":\"hciClusterRG\",\"rightColumn\":\"arcBridgeRG\"},{\"id\":\"with-vm\",\"mergeType\":\"leftouter\",\"leftTable\":\"all-clusters-base\",\"rightTable\":\"all-clusters-vm-count\",\"leftColumn\":\"hciClusterRG\",\"rightColumn\":\"arcBridgeRG\"}],\"projectRename\":[{\"originalName\":\"[all-clusters-base].ClusterName\",\"mergedName\":\"ClusterName\",\"fromId\":\"with-vm\"},{\"originalName\":\"[all-clusters-base].clusterLink\",\"mergedName\":\"clusterLink\",\"fromId\":\"with-vm\"},{\"originalName\":\"[all-clusters-base].vmLink\",\"mergedName\":\"vmLink\",\"fromId\":\"with-vm\"},{\"originalName\":\"[all-clusters-base].aksArcLink\",\"mergedName\":\"aksArcLink\",\"fromId\":\"with-vm\"},{\"originalName\":\"[all-clusters-base].machinesLink\",\"mergedName\":\"machinesLink\",\"fromId\":\"with-vm\"},{\"originalName\":\"[all-clusters-base].resourceGroup\",\"mergedName\":\"resourceGroup\",\"fromId\":\"with-vm\"},{\"originalName\":\"[all-clusters-base].status\",\"mergedName\":\"status\",\"fromId\":\"with-vm\"},{\"originalName\":\"[all-clusters-base].solutionVersion\",\"mergedName\":\"solutionVersion\",\"fromId\":\"with-vm\"},{\"originalName\":\"[all-clusters-base].nodeCount\",\"mergedName\":\"nodeCount\",\"fromId\":\"with-vm\"},{\"originalName\":\"[all-clusters-base].totalCores\",\"mergedName\":\"totalCores\",\"fromId\":\"with-vm\"},{\"originalName\":\"[all-clusters-base].totalMemoryGB\",\"mergedName\":\"totalMemoryGB\",\"fromId\":\"with-vm\"},{\"originalName\":\"[all-clusters-vm-count].VMCount\",\"mergedName\":\"VMCount\",\"fromId\":\"with-vm\"},{\"originalName\":\"[all-clusters-aksarc-count].AKSArcCount\",\"mergedName\":\"AKSArcCount\",\"fromId\":\"with-aksarc\"},{\"originalName\":\"[all-clusters-base].osVersion\",\"mergedName\":\"osVersion\",\"fromId\":\"with-vm\"},{\"originalName\":\"[all-clusters-base].hardwareClass\",\"mergedName\":\"hardwareClass\",\"fromId\":\"with-vm\"},{\"originalName\":\"[all-clusters-base].manufacturer\",\"mergedName\":\"manufacturer\",\"fromId\":\"with-vm\"},{\"originalName\":\"[all-clusters-base].model\",\"mergedName\":\"model\",\"fromId\":\"with-vm\"},{\"originalName\":\"[all-clusters-base].lastSync\",\"mergedName\":\"lastSync\",\"fromId\":\"with-vm\"},{\"originalName\":\"[all-clusters-base].azureHybridBenefit\",\"mergedName\":\"azureHybridBenefit\",\"fromId\":\"with-vm\"},{\"originalName\":\"[all-clusters-base].windowsServerSubscription\",\"mergedName\":\"windowsServerSubscription\",\"fromId\":\"with-vm\"},{\"originalName\":\"[all-clusters-base].azureVerificationForVMs\",\"mergedName\":\"azureVerificationForVMs\",\"fromId\":\"with-vm\"},{\"originalName\":\"[all-clusters-base].location\",\"mergedName\":\"location\",\"fromId\":\"with-vm\"},{\"originalName\":\"[all-clusters-base].registrationDate\",\"mergedName\":\"registrationDate\",\"fromId\":\"with-vm\"},{\"originalName\":\"[all-clusters-base].hciClusterRG\"},{\"originalName\":\"[all-clusters-base].hciClusterId\"},{\"originalName\":\"[all-clusters-aksarc-count].arcBridgeRG\"},{\"originalName\":\"[all-clusters-vm-count].arcBridgeRG\"}]}", "size": 0, "showAnalytics": true, "showRefreshButton": true, "showExportToExcel": true, "queryType": 7, "gridSettings": { "formatters": [ { "columnMatch": "ClusterName", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkColumn": "clusterLink" } }, { "columnMatch": "clusterLink", "formatter": 5 }, { "columnMatch": "status", "formatter": 18, "formatOptions": { "thresholdsOptions": "icons", "thresholdsGrid": [ { "operator": "==", "thresholdValue": "Connected", "representation": "success", "text": "{0}{1}" }, { "operator": "Default", "thresholdValue": null, "representation": "4", "text": "{0}{1}" } ] } }, { "columnMatch": "vmLink", "formatter": 5 }, { "columnMatch": "aksArcLink", "formatter": 5 }, { "columnMatch": "machinesLink", "formatter": 5 }, { "columnMatch": "nodeCount", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkColumn": "machinesLink" } }, { "columnMatch": "VMCount", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkColumn": "vmLink" } }, { "columnMatch": "AKSArcCount", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkColumn": "aksArcLink" } }, { "columnMatch": "lastSync", "formatter": 6 }, { "columnMatch": "registrationDate", "formatter": 6 }, { "columnMatch": "azureHybridBenefit", "formatter": 18, "formatOptions": { "thresholdsOptions": "icons", "thresholdsGrid": [ { "operator": "==", "thresholdValue": "Enabled", "representation": "success", "text": "{0}{1}" }, { "operator": "Default", "thresholdValue": null, "representation": "unknown", "text": "{0}{1}" } ] } }, { "columnMatch": "windowsServerSubscription", "formatter": 18, "formatOptions": { "thresholdsOptions": "icons", "thresholdsGrid": [ { "operator": "==", "thresholdValue": "Enabled", "representation": "success", "text": "{0}{1}" }, { "operator": "Default", "thresholdValue": null, "representation": "unknown", "text": "{0}{1}" } ] } }, { "columnMatch": "azureVerificationForVMs", "formatter": 18, "formatOptions": { "thresholdsOptions": "icons", "thresholdsGrid": [ { "operator": "==", "thresholdValue": "Enabled", "representation": "success", "text": "{0}{1}" }, { "operator": "Default", "thresholdValue": null, "representation": "unknown", "text": "{0}{1}" } ] } } ], "rowLimit": 2000, "filter": true, "labelSettings": [ { "columnId": "ClusterName", "label": "Cluster Name" }, { "columnId": "resourceGroup", "label": "Resource Group" }, { "columnId": "status", "label": "Status" }, { "columnId": "solutionVersion", "label": "Solution Version" }, { "columnId": "nodeCount", "label": "Machines" }, { "columnId": "totalCores", "label": "Cores" }, { "columnId": "totalMemoryGB", "label": "Memory (GB)" }, { "columnId": "VMCount", "label": "VMs" }, { "columnId": "AKSArcCount", "label": "AKS Arc" }, { "columnId": "osVersion", "label": "OS Version" }, { "columnId": "hardwareClass", "label": "Hardware Class" }, { "columnId": "manufacturer", "label": "Manufacturer" }, { "columnId": "model", "label": "Model" }, { "columnId": "lastSync", "label": "Last Sync" }, { "columnId": "azureHybridBenefit", "label": "Azure Hybrid Benefit" }, { "columnId": "windowsServerSubscription", "label": "Windows Server Subscription" }, { "columnId": "azureVerificationForVMs", "label": "Azure Verification for VMs" }, { "columnId": "location", "label": "Location" }, { "columnId": "registrationDate", "label": "Registration Date" } ] } }, "name": "table-all-clusters" }, { "type": 1, "content": { "json": "---\r\n## πŸ”‘ Azure Licensing & Verification" }, "name": "section-header-licensing" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.azurestackhci/clusters\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n| extend AHBStatus = iff(tostring(properties.softwareAssuranceProperties.softwareAssuranceStatus) =~ 'Enabled', 'Enabled', 'Disabled')\r\n| summarize Count = count() by AHBStatus", "size": 3, "title": "Azure Hybrid Benefit", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "piechart", "chartSettings": { "seriesLabelSettings": [ { "seriesName": "Enabled", "color": "green" }, { "seriesName": "Disabled", "color": "gray" } ] } }, "customWidth": "33", "name": "pie-azure-hybrid-benefit", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.azurestackhci/clusters\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n| extend WSSStatus = iff(tostring(properties.desiredProperties.windowsServerSubscription) =~ 'Enabled', 'Enabled', 'Disabled')\r\n| summarize Count = count() by WSSStatus", "size": 3, "title": "Windows Server Subscription", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "piechart", "chartSettings": { "seriesLabelSettings": [ { "seriesName": "Enabled", "color": "green" }, { "seriesName": "Disabled", "color": "gray" } ] } }, "customWidth": "33", "name": "pie-windows-server-subscription", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.azurestackhci/clusters\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n| extend AVVMStatus = iff(tostring(properties.reportedProperties.imdsAttestation) =~ 'Enabled', 'Enabled', 'Disabled')\r\n| summarize Count = count() by AVVMStatus", "size": 3, "title": "Azure Verification for VMs", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "piechart", "chartSettings": { "seriesLabelSettings": [ { "seriesName": "Enabled", "color": "green" }, { "seriesName": "Disabled", "color": "gray" } ] } }, "customWidth": "33", "name": "pie-azure-verification-vms", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 1, "content": { "json": "πŸ“š **Knowledge:** [Send Diagnostic Logs to Microsoft using Azure portal or PowerShell](https://learn.microsoft.com/azure/azure-local/manage/collect-logs?tabs=azureportal#collect-logs-for-azure-local)" }, "name": "stale-clusters-knowledge-link" }, { "type": 1, "content": { "json": "### ⚠️ Clusters Not Synced Recently\r\n*Clusters that haven't synced in the last 24 hours*" }, "name": "section-header-stale-clusters" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.azurestackhci/clusters\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n| extend lastSync = todatetime(properties.lastSyncTimestamp)\r\n| where lastSync < ago(24h)\r\n| extend hoursSinceSync = datetime_diff('hour', now(), lastSync)\r\n| extend clusterLink = strcat('https://portal.azure.com/#@/resource', id)\r\n| join kind=leftouter (resourcecontainers | where type == 'microsoft.resources/subscriptions' | project subscriptionId, subscriptionName = name) on subscriptionId\r\n| extend subscriptionLink = strcat('https://portal.azure.com/#@/resource/subscriptions/', subscriptionId)\r\n| project ClusterName = name, clusterLink, resourceGroup, lastSync, hoursSinceSync, location, SubscriptionName = subscriptionName, subscriptionLink\r\n| order by hoursSinceSync desc", "size": 0, "title": "Clusters Not Synced in 24+ Hours", "noDataMessage": "All clusters have synced within the last 24 hours", "showRefreshButton": true, "showExportToExcel": true, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "gridSettings": { "formatters": [ { "columnMatch": "ClusterName", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkColumn": "clusterLink" } }, { "columnMatch": "clusterLink", "formatter": 5 }, { "columnMatch": "lastSync", "formatter": 6 }, { "columnMatch": "hoursSinceSync", "formatter": 18, "formatOptions": { "thresholdsOptions": "icons", "thresholdsGrid": [ { "operator": ">=", "thresholdValue": "72", "representation": "4", "text": "{0}{1} hours" }, { "operator": ">=", "thresholdValue": "48", "representation": "warning", "text": "{0}{1} hours" }, { "operator": "Default", "thresholdValue": null, "representation": "Blank", "text": "{0}{1} hours" } ] } }, { "columnMatch": "SubscriptionName", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkColumn": "subscriptionLink" } }, { "columnMatch": "subscriptionLink", "formatter": 5 } ], "rowLimit": 2000, "filter": true, "labelSettings": [ { "columnId": "ClusterName", "label": "Cluster Name" }, { "columnId": "resourceGroup", "label": "Resource Group" }, { "columnId": "lastSync", "label": "Last Sync" }, { "columnId": "hoursSinceSync", "label": "Hours Since Sync" }, { "columnId": "location", "label": "Location" }, { "columnId": "SubscriptionName", "label": "Subscription Name" } ] } }, "name": "table-stale-clusters" }, { "type": 1, "content": { "json": "---\r\n## πŸ“‹ Cluster Details" }, "name": "section-header-cluster-details" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.azurestackhci/clusters\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n| extend osDisplayVersion = tostring(properties.reportedProperties.nodes[0].osDisplayVersion)\r\n| summarize Count = count() by osDisplayVersion\r\n| order by Count desc", "size": 0, "title": "OS Version Distribution", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "piechart", "chartSettings": { "showLegend": true, "legendPosition": "bottom" } }, "customWidth": "50", "name": "chart-os-version", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.azurestackhci/clusters\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n| extend hardwareClass = tostring(properties.reportedProperties.hardwareClass)\r\n| summarize Count = count() by hardwareClass\r\n| order by Count desc", "size": 0, "title": "Hardware Class Distribution", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "piechart", "chartSettings": { "showLegend": true, "legendPosition": "bottom" } }, "customWidth": "50", "name": "chart-hardware-class", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.azurestackhci/clusters\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n| extend billingModel = tostring(properties.billingModel)\r\n| summarize Count = count() by billingModel\r\n| order by Count desc", "size": 0, "title": "Billing Model", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "piechart", "chartSettings": { "showLegend": true, "legendPosition": "bottom" } }, "customWidth": "50", "name": "chart-billing-model", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.azurestackhci/clusters\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n| extend manufacturer = tostring(properties.reportedProperties.nodes[0].manufacturer)\r\n| extend model = tostring(properties.reportedProperties.nodes[0].model)\r\n| extend hwVendor = strcat(manufacturer, ' - ', model)\r\n| summarize Count = count() by hwVendor\r\n| order by Count desc", "size": 0, "title": "Hardware Vendor/Model", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "piechart", "chartSettings": { "showLegend": true, "legendPosition": "bottom" } }, "customWidth": "50", "name": "chart-hardware-vendor", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.azurestackhci/clusters\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n| extend billingModel = strcat(tostring(properties.billingModel), ' Cores')\r\n| extend nodeCount = array_length(properties.reportedProperties.nodes)\r\n| extend totalCores = toint(properties.reportedProperties.nodes[0].coreCount) * nodeCount\r\n| summarize TotalCores = sum(totalCores) by billingModel\r\n| order by TotalCores desc", "size": 0, "title": "Total Cores by Billing Model", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "piechart", "chartSettings": { "showLegend": true, "legendPosition": "bottom" } }, "customWidth": "50", "name": "chart-cores-by-billing", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.azurestackhci/clusters\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n| extend nodeCount = array_length(properties.reportedProperties.nodes)\r\n| extend nodeCountLabel = case(nodeCount == 1, '1 Node', nodeCount == 2, '2 Nodes', nodeCount == 3, '3 Nodes', strcat(tostring(nodeCount), ' Nodes'))\r\n| summarize Count = count() by nodeCountLabel\r\n| order by nodeCountLabel asc", "size": 0, "title": "Node Count Distribution", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "piechart", "chartSettings": { "showLegend": true, "legendPosition": "bottom" } }, "customWidth": "50", "name": "chart-node-count-distribution", "styleSettings": { "margin": "5px", "padding": "5px" } } ] }, "conditionalVisibility": { "parameterName": "selectedTab", "comparison": "isEqualTo", "value": "0" }, "name": "summary-tiles-group" }, { "type": 12, "content": { "version": "NotebookGroup/1.0", "groupType": "editable", "items": [ { "type": 9, "content": { "version": "KqlParameterItem/1.0", "crossComponentResources": [ "{Subscriptions}" ], "parameters": [ { "id": "cap-section-driver", "version": "KqlParameterItem/1.0", "name": "CapacitySection", "type": 1, "isHiddenWhenLocked": true, "typeSettings": { "additionalResourceOptions": [] }, "value": "overview" } ], "style": "pills", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources" }, "name": "cap-shared-params" }, { "type": 1, "content": { "json": "ℹ️ **Navigation:** The Capacity section is organised into three tabs below. Start with **πŸ“‹ Overview** for a summary of cluster capacity and top resource usage, then use **🌍 Multi-cluster** to compare trends and forecasts across your fleet, or **πŸ” Single cluster** to drill into a specific cluster, and its physical nodes for detailed capacity, storage, and workload insights." }, "name": "cap-instructions-text" }, { "type": 11, "content": { "version": "LinkItem/1.0", "style": "tabs", "tabStyle": "larger", "links": [ { "id": "tab-ov", "cellValue": "CapacitySection", "linkTarget": "parameter", "linkLabel": "πŸ“‹ Overview", "subTarget": "overview", "style": "link" }, { "id": "tab-multi", "cellValue": "CapacitySection", "linkTarget": "parameter", "linkLabel": "🌍 Multi-cluster", "subTarget": "multi", "style": "link" }, { "id": "tab-single", "cellValue": "CapacitySection", "linkTarget": "parameter", "linkLabel": "πŸ” Single cluster", "subTarget": "single", "style": "link" } ] }, "name": "cap-section-tabs" }, { "type": 12, "content": { "version": "NotebookGroup/1.0", "groupType": "editable", "title": "Overview", "items": [ { "type": 1, "content": { "json": "## πŸ—οΈ Cluster Capacity Overview\r\nPhysical cores (pCPUs), and workload virtual cores (vCPUs) allocation, allows showing you the \"P:V CPU Ratio\" per Azure Local cluster.\r\n\r\nUse the **'Target pCPU:vCPU Ratio'** drop-down box to input your preferred ratio, based on risk appetite and/or workload performance requirements (default ratio is 1:4)." }, "name": "text-capacity-overview-header" }, { "type": 9, "content": { "version": "KqlParameterItem/1.0", "parameters": [ { "id": "target-ratio-param", "version": "KqlParameterItem/1.0", "name": "TargetRatio", "label": "Target pCPU:vCPU Ratio", "type": 2, "description": "Select the target pCPU to vCPU ratio for your environment. Used for color-coding the ratio health indicator.", "isRequired": true, "typeSettings": { "additionalResourceOptions": [], "showDefault": false }, "jsonData": "[{\"value\":\"1\",\"label\":\"1:1\"},{\"value\":\"2\",\"label\":\"1:2\"},{\"value\":\"3\",\"label\":\"1:3\"},{\"value\":\"4\",\"label\":\"1:4\"},{\"value\":\"5\",\"label\":\"1:5\"},{\"value\":\"6\",\"label\":\"1:6\"}]", "value": "4" }, { "id": "ratio-filter-param", "version": "KqlParameterItem/1.0", "name": "RatioFilter", "label": "Filter by Ratio", "type": 2, "description": "Filter clusters to show only those with a pCPU to vCPU ratio at or above the selected value.", "isRequired": true, "typeSettings": { "additionalResourceOptions": [], "showDefault": false }, "jsonData": "[{\"value\":\"0\",\"label\":\"All\"},{\"value\":\"1\",\"label\":\"β‰₯ 1:1\"},{\"value\":\"2\",\"label\":\"β‰₯ 1:2\"},{\"value\":\"3\",\"label\":\"β‰₯ 1:3\"},{\"value\":\"4\",\"label\":\"β‰₯ 1:4\"},{\"value\":\"5\",\"label\":\"β‰₯ 1:5\"},{\"value\":\"6\",\"label\":\"β‰₯ 1:6\"}]", "value": "0" } ], "style": "pills", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces" }, "name": "capacity-ratio-params" }, { "type": 1, "content": { "json": "πŸ’‘ **Tip:** The 'Memory Used % (N-1)' column calculates the percentage of available memory including a N-1 formula, that deducts one machine's worth of memory for multi-machine (2+ nodes) Azure Local instances. This is required to be able to drain each machine without taking workload offline during rolling cluster update operations.", "style": "upsell" }, "name": "capacity-n1-memory-tip" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "// Cluster Capacity Overview - no let statements (ARG constraint)\r\nresources\r\n| where type == \"microsoft.azurestackhci/clusters\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n| extend hciClusterRG = tolower(resourceGroup)\r\n| extend status = iff(properties.status == \"ConnectedRecently\", \"Connected\", \"Disconnected\")\r\n| extend nodeCount = array_length(properties.reportedProperties.nodes)\r\n| extend physicalCores = toint(properties.reportedProperties.nodes[0].coreCount) * nodeCount\r\n| extend physicalMemoryGB = toint(properties.reportedProperties.nodes[0].memoryInGiB) * nodeCount\r\n| extend clusterLink = strcat('https://portal.azure.com/#@/resource', id)\r\n| extend vmLink = strcat('https://portal.azure.com/#@/resource', id, '/virtualmachines')\r\n| extend aksArcLink = strcat('https://portal.azure.com/#@/resource', id, '/kubernetesClusters')\r\n| extend machinesLink = strcat('https://portal.azure.com/#@/resource', id, '/machines')\r\n| extend storagePathLink = strcat('https://portal.azure.com/#@/resource', id, '/storagePath')\r\n// Join workload aggregation via custom location chain (single extensibilityresources ref)\r\n| join kind=leftouter (\r\n resources\r\n | where (type == \"microsoft.hybridcompute/machines\" and kind == \"HCI\")\r\n or (type == \"microsoft.kubernetes/connectedclusters\" and tostring(properties.infrastructure) == \"azure_stack_hci\")\r\n | extend isVM = (type == \"microsoft.hybridcompute/machines\")\r\n | extend vCPUs = iff(isVM, toint(properties.detectedProperties.logicalCoreCount), toint(properties.totalCoreCount))\r\n | extend memGB = iff(isVM, toint(properties.detectedProperties.totalPhysicalMemoryInGigabytes), 0)\r\n | extend resourceId = tolower(id)\r\n | join kind=inner (\r\n extensibilityresources\r\n | where type in (\"microsoft.azurestackhci/virtualmachineinstances\", \"microsoft.hybridcontainerservice/provisionedclusterinstances\")\r\n | extend customLocId = tolower(trim_end(\"/\", tostring(extendedLocation.name)))\r\n | extend parentId = tolower(iff(\r\n type == \"microsoft.azurestackhci/virtualmachineinstances\",\r\n substring(id, 0, indexof(id, \"/providers/Microsoft.AzureStackHCI\")),\r\n substring(id, 0, indexof(id, \"/providers/Microsoft.HybridContainerService\"))))\r\n | extend isProvCluster = (type == \"microsoft.hybridcontainerservice/provisionedclusterinstances\")\r\n | extend cpVmSize = iff(isProvCluster, tostring(properties.controlPlane.vmSize), \"\")\r\n | extend cpCount = iff(isProvCluster, toint(properties.controlPlane['count']), 0)\r\n | mv-expand pool = iff(isProvCluster, properties.agentPoolProfiles, dynamic([null]))\r\n | extend poolVmSize = tostring(pool.vmSize), poolCount = toint(pool['count'])\r\n | extend poolMemPerVM = case(poolVmSize =~ \"Standard_A2_v2\", 4, poolVmSize =~ \"Standard_A4_v2\", 8, poolVmSize =~ \"Standard_D2s_v3\", 8, poolVmSize =~ \"Standard_D4s_v3\", 16, poolVmSize =~ \"Standard_D8s_v3\", 32, poolVmSize =~ \"Standard_D16s_v3\", 64, poolVmSize =~ \"Standard_D32s_v3\", 128, poolVmSize =~ \"Standard_NC4_A2\", 28, poolVmSize =~ \"Standard_NC8_A2\", 56, poolVmSize =~ \"Standard_NC16_A2\", 112, 0)\r\n | extend cpMemPerVM = case(cpVmSize =~ \"Standard_A2_v2\", 4, cpVmSize =~ \"Standard_A4_v2\", 8, cpVmSize =~ \"Standard_D2s_v3\", 8, cpVmSize =~ \"Standard_D4s_v3\", 16, cpVmSize =~ \"Standard_D8s_v3\", 32, cpVmSize =~ \"Standard_D16s_v3\", 64, cpVmSize =~ \"Standard_D32s_v3\", 128, cpVmSize =~ \"Standard_NC4_A2\", 28, cpVmSize =~ \"Standard_NC8_A2\", 56, cpVmSize =~ \"Standard_NC16_A2\", 112, 0)\r\n | summarize customLocId = take_any(customLocId), poolMemGB = sum(poolCount * poolMemPerVM), cpCount = take_any(cpCount), cpMemPerVM = take_any(cpMemPerVM) by parentId\r\n | extend aksMemGB = poolMemGB + (cpCount * cpMemPerVM)\r\n | project parentId, customLocId, aksMemGB\r\n ) on $left.resourceId == $right.parentId\r\n | join kind=inner (\r\n resources\r\n | where type == \"microsoft.extendedlocation/customlocations\"\r\n | extend arcBridgeRG = tolower(tostring(split(tostring(properties.hostResourceId), '/')[4]))\r\n | project customLocId = tolower(trim_end(\"/\", id)), arcBridgeRG\r\n ) on customLocId\r\n | summarize vmVCPUTotal = sumif(vCPUs, isVM), vmMemoryTotalGB = sumif(memGB, isVM), aksVCPUTotal = sumif(vCPUs, not(isVM)), aksMemoryTotalGB = sumif(aksMemGB, not(isVM)) by arcBridgeRG\r\n) on $left.hciClusterRG == $right.arcBridgeRG\r\n// Fallback for disconnected clusters - get node data from Arc machines\r\n| join kind=leftouter (\r\n resources\r\n | where type == \"microsoft.hybridcompute/machines\" and properties.cloudMetadata.provider == \"AzSHCI\" and kind != \"HCI\"\r\n | summarize arcNodeCount = count(), arcLogicalCoresPerNode = take_any(toint(properties.detectedProperties.logicalCoreCount)), arcMemGiBPerNode = take_any(toint(properties.detectedProperties.totalPhysicalMemoryInGigabytes)) by arcClusterRG = tolower(resourceGroup)\r\n) on $left.hciClusterRG == $right.arcClusterRG\r\n// Storage volume aggregation\r\n| join kind=leftouter (\r\n resources\r\n | where type == \"microsoft.azurestackhci/storagecontainers\"\r\n | extend storageCustomLocKey = tolower(trim_end(\"/\", tostring(extendedLocation.name)))\r\n | join kind=inner (\r\n resources\r\n | where type == \"microsoft.extendedlocation/customlocations\"\r\n | extend storageArcBridgeRG = tolower(tostring(split(tostring(properties.hostResourceId), '/')[4]))\r\n | project storageCustomLocKey = tolower(trim_end(\"/\", id)), storageArcBridgeRG\r\n ) on storageCustomLocKey\r\n | summarize storageTotalMB = sum(toreal(properties.status.containerSizeMB)), storageAvailMB = sum(toreal(properties.status.availableSizeMB)) by storageArcBridgeRG\r\n) on $left.hciClusterRG == $right.storageArcBridgeRG\r\n| extend nodeCount = iff(nodeCount > 0, nodeCount, coalesce(arcNodeCount, 0))\r\n| extend physicalCores = iff(physicalCores > 0, physicalCores, coalesce(toint(arcLogicalCoresPerNode / 2) * arcNodeCount, 0))\r\n| extend physicalMemoryGB = iff(physicalMemoryGB > 0, physicalMemoryGB, coalesce(arcMemGiBPerNode * arcNodeCount, 0))\r\n// Compute totals and ratio\r\n| extend vmVCPUTotal = coalesce(vmVCPUTotal, 0)\r\n| extend vmMemoryTotalGB = coalesce(vmMemoryTotalGB, 0)\r\n| extend aksMemoryTotalGB = coalesce(aksMemoryTotalGB, 0)\r\n| extend aksVCPUTotal = coalesce(aksVCPUTotal, 0)\r\n| extend vmMemoryTotalGB = vmMemoryTotalGB + aksMemoryTotalGB\r\n| extend vCPUTotal = vmVCPUTotal + aksVCPUTotal\r\n| extend ratioValue = iff(physicalCores > 0 and vCPUTotal > 0, round(todouble(vCPUTotal) / todouble(physicalCores), 1), 0.0)\r\n| extend pCPUvCPURatio = iff(ratioValue > 0, strcat('1:', tostring(ratioValue)), 'N/A')\r\n| extend effectiveMemoryGB = iff(nodeCount >= 2, physicalMemoryGB - (physicalMemoryGB / nodeCount), physicalMemoryGB)\r\n| extend memoryUsagePct = iff(effectiveMemoryGB > 0 and vmMemoryTotalGB > 0, round(todouble(vmMemoryTotalGB) / todouble(effectiveMemoryGB) * 100.0, 1), 0.0)\r\n| extend ratioHealthPct = iff(ratioValue > 0 and todouble('{TargetRatio}') > 0, round(ratioValue / todouble('{TargetRatio}') * 100.0, 0), 0.0)\r\n| where '{RatioFilter}' == '0' or ratioValue >= todouble('{RatioFilter}')\r\n// Storage columns\r\n| extend storageUsedGB = iff(isnotempty(storageTotalMB), round((storageTotalMB - storageAvailMB) / 1024, 0), 0.0)\r\n| extend storageAvailGB = iff(isnotempty(storageTotalMB), round(storageAvailMB / 1024, 0), 0.0)\r\n| extend storageTotalGB = storageUsedGB + storageAvailGB\r\n| extend storageUsedPct = iff(storageTotalGB > 0, round(storageUsedGB / storageTotalGB * 100.0, 1), 0.0)\r\n| project ClusterName = name, clusterLink, vmLink, aksArcLink, machinesLink, storagePathLink, status, nodeCount, pCPUvCPURatio, ratioValue, ratioHealthPct, memoryUsagePct, storageUsedPct, physicalCores, vCPUTotal, physicalMemoryGB, vmMemoryTotalGB, vmVCPUTotal, aksVCPUTotal, storageUsedGB, storageAvailGB\r\n| order by memoryUsagePct desc, ratioHealthPct desc, ClusterName asc", "size": 0, "showAnalytics": true, "title": "Cluster Capacity Overview", "showRefreshButton": true, "showExportToExcel": true, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "table", "gridSettings": { "formatters": [ { "columnMatch": "ClusterName", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkColumn": "clusterLink" } }, { "columnMatch": "clusterLink", "formatter": 5 }, { "columnMatch": "vmLink", "formatter": 5 }, { "columnMatch": "aksArcLink", "formatter": 5 }, { "columnMatch": "machinesLink", "formatter": 5 }, { "columnMatch": "storagePathLink", "formatter": 5 }, { "columnMatch": "status", "formatter": 18, "formatOptions": { "thresholdsOptions": "icons", "thresholdsGrid": [ { "operator": "==", "thresholdValue": "Connected", "representation": "success", "text": "{0}{1}" }, { "operator": "Default", "thresholdValue": null, "representation": "4", "text": "{0}{1}" } ] } }, { "columnMatch": "nodeCount", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkColumn": "machinesLink" } }, { "columnMatch": "pCPUvCPURatio", "formatter": 5 }, { "columnMatch": "ratioValue", "formatter": 18, "formatOptions": { "customColumnWidthSetting": "160px", "thresholdsOptions": "colors", "thresholdsGrid": [ { "operator": "==", "thresholdValue": "0", "representation": null, "text": "N/A" }, { "operator": "Default", "thresholdValue": null, "representation": null, "text": "1:{0}" } ] }, "numberFormat": { "unit": 0, "options": { "style": "decimal", "minimumFractionDigits": 0, "maximumFractionDigits": 1 } } }, { "columnMatch": "ratioHealthPct", "formatter": 8, "formatOptions": { "min": 0, "max": 200, "palette": "greenRedDark" }, "numberFormat": { "unit": 1, "options": { "style": "decimal", "minimumFractionDigits": 0, "maximumFractionDigits": 0 } } }, { "columnMatch": "memoryUsagePct", "formatter": 18, "formatOptions": { "thresholdsOptions": "icons", "thresholdsGrid": [ { "operator": ">=", "thresholdValue": "95", "representation": "4", "text": "{0}{1}" }, { "operator": ">=", "thresholdValue": "80", "representation": "2", "text": "{0}{1}" }, { "operator": "Default", "thresholdValue": null, "representation": "success", "text": "{0}{1}" } ] }, "numberFormat": { "unit": 1, "options": { "style": "decimal", "minimumFractionDigits": 1, "maximumFractionDigits": 1 } } }, { "columnMatch": "physicalCores", "formatter": 0, "numberFormat": { "unit": 0, "options": { "style": "decimal", "minimumFractionDigits": 0, "maximumFractionDigits": 0 } } }, { "columnMatch": "physicalMemoryGB", "formatter": 0, "numberFormat": { "unit": 5, "options": { "style": "decimal", "minimumFractionDigits": 0, "maximumFractionDigits": 0 } } }, { "columnMatch": "vmVCPUTotal", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkColumn": "vmLink" } }, { "columnMatch": "aksVCPUTotal", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkColumn": "aksArcLink" } }, { "columnMatch": "vCPUTotal", "formatter": 0, "numberFormat": { "unit": 0, "options": { "style": "decimal", "minimumFractionDigits": 0, "maximumFractionDigits": 0 } } }, { "columnMatch": "vmMemoryTotalGB", "formatter": 0, "numberFormat": { "unit": 5, "options": { "style": "decimal", "minimumFractionDigits": 1, "maximumFractionDigits": 1 } } }, { "columnMatch": "storageUsedGB", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkColumn": "storagePathLink" }, "numberFormat": { "unit": 5, "options": { "style": "decimal", "minimumFractionDigits": 0, "maximumFractionDigits": 0 } } }, { "columnMatch": "storageAvailGB", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkColumn": "storagePathLink" }, "numberFormat": { "unit": 5, "options": { "style": "decimal", "minimumFractionDigits": 0, "maximumFractionDigits": 0 } } }, { "columnMatch": "storageUsedPct", "formatter": 18, "formatOptions": { "thresholdsOptions": "icons", "thresholdsGrid": [ { "operator": "==", "thresholdValue": "0", "representation": "Blank", "text": "Unknown" }, { "operator": ">=", "thresholdValue": "95", "representation": "4", "text": "{0}{1}" }, { "operator": ">=", "thresholdValue": "80", "representation": "2", "text": "{0}{1}" }, { "operator": "Default", "thresholdValue": null, "representation": "success", "text": "{0}{1}" } ] }, "numberFormat": { "unit": 1, "options": { "style": "decimal", "minimumFractionDigits": 1, "maximumFractionDigits": 1 } } } ], "rowLimit": 2000, "filter": true, "labelSettings": [ { "columnId": "ClusterName", "label": "Cluster" }, { "columnId": "status", "label": "Status" }, { "columnId": "nodeCount", "label": "Machines" }, { "columnId": "pCPUvCPURatio", "label": "P:V CPU Ratio" }, { "columnId": "ratioValue", "label": "P:V CPU Ratio" }, { "columnId": "ratioHealthPct", "label": "% of P:V Target" }, { "columnId": "memoryUsagePct", "label": "Memory Used % (N-1)" }, { "columnId": "storageUsedPct", "label": "Storage Used %" }, { "columnId": "physicalCores", "label": "pCPUs" }, { "columnId": "vCPUTotal", "label": "vCPUs" }, { "columnId": "physicalMemoryGB", "label": "Physical Memory" }, { "columnId": "vmMemoryTotalGB", "label": "Workload Memory" }, { "columnId": "vmVCPUTotal", "label": "VM vCPUs" }, { "columnId": "aksVCPUTotal", "label": "AKS vCPUs" }, { "columnId": "storageUsedGB", "label": "Storage Used" }, { "columnId": "storageAvailGB", "label": "Storage Available" } ] }, "sortBy": [] }, "name": "capacity-overview-table" }, { "type": 1, "content": { "json": "### πŸ“Š Top 5 Azure Local Instances by Resource Capacity Usage\r\nShowing the 5 clusters with the highest average usage for each resource type over the selected time range." }, "name": "text-top5-charts-header" }, { "type": 1, "content": { "json": "πŸ’‘ **Tip:** The six resource graphs below use data in Azure Log Analytics, this requires that your clusters have the Azure Monitoring Agent (AMA) extension installed, for more information see [Azure Local Insights](https://learn.microsoft.com/azure/azure-local/concepts/monitoring-overview).", "style": "upsell" }, "name": "capacity-ama-tip" }, { "type": 9, "content": { "version": "KqlParameterItem/1.0", "crossComponentResources": [ "{Subscriptions}" ], "parameters": [ { "id": "machines-log-analytics-workspace-ov", "version": "KqlParameterItem/1.0", "name": "MachinesLogAnalyticsWorkspace", "label": "Log Analytics Workspace", "type": 2, "description": "Select one or more Log Analytics workspaces collecting Azure Local node performance data. Defaults to All.", "isRequired": true, "multiSelect": true, "quote": "'", "delimiter": ",", "query": "resources\r\n| where type == \"microsoft.operationalinsights/workspaces\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| project value = id, label = name\r\n| order by label asc", "crossComponentResources": [ "{Subscriptions}" ], "typeSettings": { "additionalResourceOptions": [ "value::all" ], "includeAll": true, "showDefault": false }, "defaultValue": "value::all", "value": [ "value::all" ], "queryType": 1, "resourceType": "microsoft.resourcegraph/resources" }, { "id": "node-trends-time-range-ov", "version": "KqlParameterItem/1.0", "name": "NodeTrendsTimeRange", "label": "Historic Time Range", "type": 4, "description": "Time range for the Top 5 resource usage charts below.", "isRequired": true, "typeSettings": { "selectableValues": [ { "durationMs": 1800000 }, { "durationMs": 3600000 }, { "durationMs": 14400000 }, { "durationMs": 43200000 }, { "durationMs": 86400000 }, { "durationMs": 259200000 }, { "durationMs": 604800000 }, { "durationMs": 1209600000 }, { "durationMs": 2592000000 } ], "allowCustom": true }, "value": { "durationMs": 259200000 } } ], "style": "pills", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources" }, "name": "overview-la-workspace-param" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "let trendStart = {NodeTrendsTimeRange:start};\r\nlet trendEnd = {NodeTrendsTimeRange:end};\r\nlet step = 1d;\r\nlet clusterMap = materialize(\r\n print mapping = dynamic({ClusterRGMap})\r\n | mv-expand mapping to typeof(string)\r\n | extend parts = split(mapping, ':')\r\n | project nodeRG = tolower(tostring(parts[0])), clusterName = tostring(parts[1])\r\n);\r\nlet hciNodes = materialize(Heartbeat\r\n| where TimeGenerated between (trendStart .. trendEnd)\r\n| where OSType == \"Windows\"\r\n| summarize arg_max(TimeGenerated, *) by Computer\r\n| project Computer, nodeRG = tolower(ResourceGroup)\r\n| lookup kind=inner clusterMap on nodeRG\r\n| project Computer, clusterName);\r\nlet top5 = Perf\r\n| where TimeGenerated between (trendStart .. trendEnd)\r\n| where ObjectName == \"Processor\" and CounterName == \"% Processor Time\" and InstanceName == \"_Total\"\r\n| lookup kind=inner hciNodes on Computer\r\n| summarize AvgUsage = avg(CounterValue) by clusterName\r\n| top 5 by AvgUsage desc\r\n| project clusterName;\r\nPerf\r\n| where TimeGenerated between (trendStart .. trendEnd)\r\n| where ObjectName == \"Processor\" and CounterName == \"% Processor Time\" and InstanceName == \"_Total\"\r\n| lookup kind=inner hciNodes on Computer\r\n| lookup kind=inner top5 on clusterName\r\n| summarize [\"Avg Usage %\"] = round(avg(CounterValue), 1) by bin(TimeGenerated, step), clusterName\r\n| order by TimeGenerated asc", "size": 1, "aggregation": 3, "title": "CPU Usage (%) β€” Top 5 Clusters", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", "crossComponentResources": [ "{MachinesLogAnalyticsWorkspace}" ], "visualization": "linechart", "chartSettings": { "xAxis": "TimeGenerated", "yAxis": [ "Avg Usage %" ], "group": "clusterName", "showLegend": true, "ySettings": { "min": 0, "max": 100, "numberFormatSettings": { "unit": 1, "options": { "style": "decimal", "maximumFractionDigits": 1 } } }, "seriesLabelSettings": [] } }, "customWidth": "33", "name": "node-cpu-trend", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "let trendStart = {NodeTrendsTimeRange:start};\r\nlet trendEnd = {NodeTrendsTimeRange:end};\r\nlet step = 1d;\r\nlet clusterMap = materialize(\r\n print mapping = dynamic({ClusterRGMap})\r\n | mv-expand mapping to typeof(string)\r\n | extend parts = split(mapping, ':')\r\n | project nodeRG = tolower(tostring(parts[0])), clusterName = tostring(parts[1])\r\n);\r\nlet hciNodes = materialize(Heartbeat\r\n| where TimeGenerated between (trendStart .. trendEnd)\r\n| where OSType == \"Windows\"\r\n| summarize arg_max(TimeGenerated, *) by Computer\r\n| project Computer, nodeRG = tolower(ResourceGroup)\r\n| lookup kind=inner clusterMap on nodeRG\r\n| project Computer, clusterName);\r\nlet top5 = Perf\r\n| where TimeGenerated between (trendStart .. trendEnd)\r\n| where ObjectName == \"Memory\" and CounterName == \"% Committed Bytes In Use\"\r\n| lookup kind=inner hciNodes on Computer\r\n| summarize AvgUsage = avg(CounterValue) by clusterName\r\n| top 5 by AvgUsage desc\r\n| project clusterName;\r\nPerf\r\n| where TimeGenerated between (trendStart .. trendEnd)\r\n| where ObjectName == \"Memory\" and CounterName == \"% Committed Bytes In Use\"\r\n| lookup kind=inner hciNodes on Computer\r\n| lookup kind=inner top5 on clusterName\r\n| summarize [\"Avg Usage %\"] = round(avg(CounterValue), 1) by bin(TimeGenerated, step), clusterName\r\n| order by TimeGenerated asc", "size": 1, "aggregation": 3, "title": "Memory Usage (%) β€” Top 5 Clusters", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", "crossComponentResources": [ "{MachinesLogAnalyticsWorkspace}" ], "visualization": "linechart", "chartSettings": { "xAxis": "TimeGenerated", "yAxis": [ "Avg Usage %" ], "group": "clusterName", "showLegend": true, "ySettings": { "min": 0, "max": 100, "numberFormatSettings": { "unit": 1, "options": { "style": "decimal", "maximumFractionDigits": 1 } } }, "seriesLabelSettings": [] } }, "customWidth": "33", "name": "node-memory-trend", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "let trendStart = {NodeTrendsTimeRange:start};\r\nlet trendEnd = {NodeTrendsTimeRange:end};\r\nlet step = 1d;\r\nlet clusterMap = materialize(\r\n print mapping = dynamic({ClusterRGMap})\r\n | mv-expand mapping to typeof(string)\r\n | extend parts = split(mapping, ':')\r\n | project nodeRG = tolower(tostring(parts[0])), clusterName = tostring(parts[1])\r\n);\r\nlet hciNodes = materialize(Heartbeat\r\n| where TimeGenerated between (trendStart .. trendEnd)\r\n| where OSType == \"Windows\"\r\n| summarize arg_max(TimeGenerated, *) by Computer\r\n| project Computer, nodeRG = tolower(ResourceGroup)\r\n| lookup kind=inner clusterMap on nodeRG\r\n| project Computer, clusterName);\r\nlet perfStorage = Perf\r\n| where TimeGenerated between (trendStart .. trendEnd)\r\n| where (ObjectName == \"LogicalDisk\" and CounterName in (\"% Free Space\", \"% Used Space\", \"% Disk Space Used\"))\r\n or (ObjectName == \"Cluster CSV File System\" and CounterName in (\"% Used Space\", \"% Free Space\"))\r\n or (ObjectName == \"Cluster Shared Volume\" and CounterName in (\"% Free Space\", \"% Used Space\"))\r\n| where InstanceName != \"_Total\" and InstanceName != \"HarddiskVolume1\"\r\n| extend UsedPercent = iff(CounterName == \"% Free Space\", 100.0 - CounterValue, CounterValue)\r\n| project TimeGenerated, Computer, UsedPercent;\r\nlet insightsStorage = InsightsMetrics\r\n| where TimeGenerated between (trendStart .. trendEnd)\r\n| where Namespace == \"LogicalDisk\" and Name == \"FreeSpacePercentage\"\r\n| extend Computer = tostring(split(_ResourceId, \"/\")[8])\r\n| extend UsedPercent = 100.0 - Val\r\n| project TimeGenerated, Computer, UsedPercent;\r\nlet allStorage = materialize(union perfStorage, insightsStorage\r\n| lookup kind=inner hciNodes on Computer);\r\nlet top5 = allStorage\r\n| summarize AvgUsage = avg(UsedPercent) by clusterName\r\n| top 5 by AvgUsage desc\r\n| project clusterName;\r\nallStorage\r\n| lookup kind=inner top5 on clusterName\r\n| summarize [\"Avg Usage %\"] = round(avg(UsedPercent), 1) by bin(TimeGenerated, step), clusterName\r\n| order by TimeGenerated asc", "size": 1, "aggregation": 3, "title": "Storage Usage (%) β€” Top 5 Clusters", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", "crossComponentResources": [ "{MachinesLogAnalyticsWorkspace}" ], "visualization": "linechart", "chartSettings": { "xAxis": "TimeGenerated", "yAxis": [ "Avg Usage %" ], "group": "clusterName", "showLegend": true, "ySettings": { "min": 0, "max": 100, "numberFormatSettings": { "unit": 1, "options": { "style": "decimal", "maximumFractionDigits": 1 } } }, "seriesLabelSettings": [] } }, "customWidth": "33", "name": "node-storage-trend", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "let trendStart = {NodeTrendsTimeRange:start};\r\nlet trendEnd = {NodeTrendsTimeRange:end};\r\nlet step = 1d;\r\nlet clusterMap = materialize(\r\n print mapping = dynamic({ClusterRGMap})\r\n | mv-expand mapping to typeof(string)\r\n | extend parts = split(mapping, ':')\r\n | project nodeRG = tolower(tostring(parts[0])), clusterName = tostring(parts[1])\r\n);\r\nlet hciNodes = materialize(Heartbeat\r\n| where TimeGenerated between (trendStart .. trendEnd)\r\n| where OSType == \"Windows\"\r\n| summarize arg_max(TimeGenerated, *) by Computer\r\n| project Computer, nodeRG = tolower(ResourceGroup)\r\n| lookup kind=inner clusterMap on nodeRG\r\n| project Computer, clusterName);\r\nlet perfLatency = Perf\r\n| where TimeGenerated between (trendStart .. trendEnd)\r\n| where (ObjectName == \"LogicalDisk\" and CounterName in (\"Avg. Disk sec/Read\", \"Avg. Disk sec/Write\"))\r\n or (ObjectName == \"Cluster CSV File System\" and CounterName in (\"Avg. Disk sec/Read\", \"Avg. Disk sec/Write\"))\r\n or (ObjectName == \"Cluster Shared Volume\" and CounterName in (\"Avg. Disk sec/Read\", \"Avg. Disk sec/Write\"))\r\n| where InstanceName != \"_Total\" and InstanceName != \"HarddiskVolume1\"\r\n| extend LatencyMs = CounterValue * 1000.0\r\n| project TimeGenerated, Computer, LatencyMs;\r\nlet insightsLatency = InsightsMetrics\r\n| where TimeGenerated between (trendStart .. trendEnd)\r\n| where Namespace == \"LogicalDisk\" and Name in (\"ReadLatencyMs\", \"WriteLatencyMs\")\r\n| extend Computer = tostring(split(_ResourceId, \"/\")[8])\r\n| extend LatencyMs = Val\r\n| project TimeGenerated, Computer, LatencyMs;\r\nlet allLatency = materialize(union perfLatency, insightsLatency\r\n| lookup kind=inner hciNodes on Computer);\r\nlet top5 = allLatency\r\n| summarize AvgLatency = avg(LatencyMs) by clusterName\r\n| top 5 by AvgLatency desc\r\n| project clusterName;\r\nallLatency\r\n| lookup kind=inner top5 on clusterName\r\n| summarize [\"Avg Latency (ms)\"] = round(avg(LatencyMs), 2) by bin(TimeGenerated, step), clusterName\r\n| order by TimeGenerated asc", "size": 1, "aggregation": 3, "title": "Storage Latency (ms) β€” Top 5 Clusters", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", "crossComponentResources": [ "{MachinesLogAnalyticsWorkspace}" ], "visualization": "linechart", "chartSettings": { "xAxis": "TimeGenerated", "yAxis": [ "Avg Latency (ms)" ], "group": "clusterName", "showLegend": true, "ySettings": { "min": 0, "numberFormatSettings": { "unit": 0, "options": { "style": "decimal", "maximumFractionDigits": 2 } } }, "seriesLabelSettings": [] } }, "customWidth": "33", "name": "node-storage-latency-trend", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "let trendStart = {NodeTrendsTimeRange:start};\r\nlet trendEnd = {NodeTrendsTimeRange:end};\r\nlet step = 1d;\r\nlet clusterMap = materialize(\r\n print mapping = dynamic({ClusterRGMap})\r\n | mv-expand mapping to typeof(string)\r\n | extend parts = split(mapping, ':')\r\n | project nodeRG = tolower(tostring(parts[0])), clusterName = tostring(parts[1])\r\n);\r\nlet hciNodes = materialize(Heartbeat\r\n| where TimeGenerated between (trendStart .. trendEnd)\r\n| where OSType == \"Windows\"\r\n| summarize arg_max(TimeGenerated, *) by Computer\r\n| project Computer, nodeRG = tolower(ResourceGroup)\r\n| lookup kind=inner clusterMap on nodeRG\r\n| project Computer, clusterName);\r\nlet perfIOPS = Perf\r\n| where TimeGenerated between (trendStart .. trendEnd)\r\n| where (ObjectName == \"LogicalDisk\" and CounterName == \"Disk Transfers/sec\")\r\n or (ObjectName == \"Cluster CSV File System\" and CounterName == \"Disk Transfers/sec\")\r\n or (ObjectName == \"Cluster Shared Volume\" and CounterName == \"Disk Transfers/sec\")\r\n| where InstanceName != \"_Total\" and InstanceName != \"HarddiskVolume1\"\r\n| project TimeGenerated, Computer, IOPS = CounterValue;\r\nlet insightsIOPS = InsightsMetrics\r\n| where TimeGenerated between (trendStart .. trendEnd)\r\n| where Namespace == \"LogicalDisk\" and Name in (\"ReadsPerSecond\", \"WritesPerSecond\")\r\n| extend Computer = tostring(split(_ResourceId, \"/\")[8])\r\n| project TimeGenerated, Computer, IOPS = Val;\r\nlet allIOPS = materialize(union perfIOPS, insightsIOPS\r\n| lookup kind=inner hciNodes on Computer);\r\nlet top5 = allIOPS\r\n| summarize AvgIOPS = avg(IOPS) by clusterName\r\n| top 5 by AvgIOPS desc\r\n| project clusterName;\r\nallIOPS\r\n| lookup kind=inner top5 on clusterName\r\n| summarize [\"Avg IOPS\"] = round(avg(IOPS), 0) by bin(TimeGenerated, step), clusterName\r\n| order by TimeGenerated asc", "size": 1, "aggregation": 3, "title": "Storage IOPS β€” Top 5 Clusters", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", "crossComponentResources": [ "{MachinesLogAnalyticsWorkspace}" ], "visualization": "linechart", "chartSettings": { "xAxis": "TimeGenerated", "yAxis": [ "Avg IOPS" ], "group": "clusterName", "showLegend": true, "ySettings": { "min": 0, "numberFormatSettings": { "unit": 0, "options": { "style": "decimal", "maximumFractionDigits": 0 } } }, "seriesLabelSettings": [] } }, "customWidth": "33", "name": "node-storage-iops-trend", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "let trendStart = {NodeTrendsTimeRange:start};\r\nlet trendEnd = {NodeTrendsTimeRange:end};\r\nlet step = 1d;\r\nlet clusterMap = materialize(\r\n print mapping = dynamic({ClusterRGMap})\r\n | mv-expand mapping to typeof(string)\r\n | extend parts = split(mapping, ':')\r\n | project nodeRG = tolower(tostring(parts[0])), clusterName = tostring(parts[1])\r\n);\r\nlet hciNodes = materialize(Heartbeat\r\n| where TimeGenerated between (trendStart .. trendEnd)\r\n| where OSType == \"Windows\"\r\n| summarize arg_max(TimeGenerated, *) by Computer\r\n| project Computer, nodeRG = tolower(ResourceGroup)\r\n| lookup kind=inner clusterMap on nodeRG\r\n| project Computer, clusterName);\r\nlet perfNet = Perf\r\n| where TimeGenerated between (trendStart .. trendEnd)\r\n| where ObjectName in (\"Network Adapter\", \"Network Interface\") and CounterName == \"Bytes Total/sec\"\r\n| where InstanceName != \"_Total\"\r\n| summarize BytesPerSec = sum(CounterValue) by TimeGenerated, Computer;\r\nlet insightsNet = InsightsMetrics\r\n| where TimeGenerated between (trendStart .. trendEnd)\r\n| where Namespace == \"Network\" and Name in (\"WriteBytesPerSecond\", \"ReadBytesPerSecond\")\r\n| extend Computer = tostring(split(_ResourceId, \"/\")[8])\r\n| summarize BytesPerSec = sum(Val) by TimeGenerated, Computer;\r\nlet allNet = materialize(union perfNet, insightsNet\r\n| lookup kind=inner hciNodes on Computer);\r\nlet top5 = allNet\r\n| summarize AvgMBps = avg(BytesPerSec) / 1048576.0 by clusterName\r\n| top 5 by AvgMBps desc\r\n| project clusterName;\r\nallNet\r\n| lookup kind=inner top5 on clusterName\r\n| summarize [\"Avg Throughput (MB/s)\"] = round(avg(BytesPerSec) / 1048576.0, 1) by bin(TimeGenerated, step), clusterName\r\n| order by TimeGenerated asc", "size": 1, "aggregation": 3, "title": "Network Throughput (MB/s) β€” Top 5 Clusters", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", "crossComponentResources": [ "{MachinesLogAnalyticsWorkspace}" ], "visualization": "linechart", "chartSettings": { "xAxis": "TimeGenerated", "yAxis": [ "Avg Throughput (MB/s)" ], "group": "clusterName", "showLegend": true, "ySettings": { "min": 0, "numberFormatSettings": { "unit": 0, "options": { "style": "decimal", "maximumFractionDigits": 1 } } }, "seriesLabelSettings": [] } }, "customWidth": "33", "name": "node-network-throughput-trend", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 1, "content": { "json": "---\r\n## πŸ“ˆ AKS Node Performance (Azure Managed Prometheus)\r\nThe charts below use Prometheus metrics from Azure Monitor Workspace. Requires Azure Managed Prometheus enabled on AKS Arc clusters." }, "name": "ov-prometheus-header" }, { "type": 9, "content": { "version": "KqlParameterItem/1.0", "parameters": [ { "id": "azure-monitor-workspace", "version": "KqlParameterItem/1.0", "name": "AzureMonitorWorkspace", "label": "Azure Monitor Workspace", "type": 2, "description": "Select the Azure Monitor Workspace collecting Prometheus metrics from your AKS Arc clusters.", "isRequired": true, "multiSelect": true, "quote": "'", "delimiter": ",", "query": "resources\r\n| where type == \"microsoft.monitor/accounts\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| project value = id, label = name\r\n| order by label asc", "crossComponentResources": [ "{Subscriptions}" ], "typeSettings": { "additionalResourceOptions": [ "value::all" ], "includeAll": true, "showDefault": false }, "defaultValue": "value::all", "value": [ "value::all" ], "queryType": 1, "resourceType": "microsoft.resourcegraph/resources" }, { "id": "prometheus-time-range", "version": "KqlParameterItem/1.0", "name": "PrometheusTimeRange", "label": "Prometheus Time Range", "type": 4, "description": "Time range for Prometheus charts. Shorter ranges provide higher resolution data.", "isRequired": true, "typeSettings": { "selectableValues": [ { "durationMs": 1800000 }, { "durationMs": 3600000 }, { "durationMs": 14400000 }, { "durationMs": 43200000 }, { "durationMs": 86400000 }, { "durationMs": 259200000 }, { "durationMs": 604800000 }, { "durationMs": 1209600000 }, { "durationMs": 2592000000 } ], "allowCustom": true }, "value": { "durationMs": 259200000 } } ], "style": "pills", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources" }, "conditionalVisibility": { "parameterName": "ClusterFilter", "comparison": "isNotEqualTo", "value": "value::all" }, "name": "aks-prometheus-params" }, { "type": 1, "content": { "json": "πŸ’‘ **Tip:** Use the **'Azure Monitor Workspace'** filter to view data in the charts below. These require [Azure Managed Prometheus](https://learn.microsoft.com/en-us/azure/azure-monitor/metrics/prometheus-metrics-overview) to be enabled on your AKS Arc clusters. Prometheus metrics are collected into an Azure Monitor Workspace and queried using PromQL.", "style": "upsell" }, "conditionalVisibility": { "parameterName": "ClusterFilter", "comparison": "isNotEqualTo", "value": "value::all" }, "name": "text-prometheus-tip" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "{\"version\":\"PrometheusQueryProvider/1.0\",\"customEndpoint\":false,\"queryText\":\"topk(5, avg by (cluster, instance) (1 - rate(node_cpu_seconds_total{mode=\\\"idle\\\"}[5m])) * 100)\",\"type\":\"query_range\"}", "size": 0, "aggregation": 3, "title": "πŸ“ˆ Top 5 AKS Nodes by CPU Usage", "noDataMessage": "No Prometheus data found. Ensure Azure Managed Prometheus is enabled and the selected Azure Monitor Workspace receives metrics.", "timeContextFromParameter": "PrometheusTimeRange", "queryType": 16, "resourceType": "microsoft.monitor/accounts", "crossComponentResources": [ "{AzureMonitorWorkspace}" ], "visualization": "timechart", "chartSettings": { "group": "*", "createOtherGroup": 50, "ySettings": { "min": 0, "max": 100, "numberFormatSettings": { "unit": 1, "options": { "style": "decimal", "useGrouping": true, "minimumFractionDigits": 0, "maximumFractionDigits": 1 } } }, "showLegend": true } }, "customWidth": "50", "conditionalVisibility": { "parameterName": "ClusterFilter", "comparison": "isNotEqualTo", "value": "value::all" }, "name": "cluster-aks-node-cpu-chart" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "{\"version\":\"PrometheusQueryProvider/1.0\",\"customEndpoint\":false,\"queryText\":\"topk(5, (1 - avg by (cluster, instance) (node_memory_MemAvailable_bytes) / avg by (cluster, instance) (node_memory_MemTotal_bytes)) * 100)\",\"type\":\"query_range\"}", "size": 0, "aggregation": 3, "title": "πŸ“ˆ Top 5 AKS Nodes by Memory Usage", "noDataMessage": "No Prometheus data found. Ensure Azure Managed Prometheus is enabled and the selected Azure Monitor Workspace receives metrics.", "timeContextFromParameter": "PrometheusTimeRange", "queryType": 16, "resourceType": "microsoft.monitor/accounts", "crossComponentResources": [ "{AzureMonitorWorkspace}" ], "visualization": "timechart", "chartSettings": { "group": "*", "createOtherGroup": 50, "ySettings": { "min": 0, "max": 100, "numberFormatSettings": { "unit": 1, "options": { "style": "decimal", "useGrouping": true, "minimumFractionDigits": 0, "maximumFractionDigits": 1 } } }, "showLegend": true } }, "customWidth": "50", "conditionalVisibility": { "parameterName": "ClusterFilter", "comparison": "isNotEqualTo", "value": "value::all" }, "name": "cluster-aks-node-memory-chart" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "{\"version\":\"PrometheusQueryProvider/1.0\",\"customEndpoint\":false,\"queryText\":\"topk(5, avg by (cluster, instance) (rate(node_disk_read_bytes_total[5m]) + rate(node_disk_written_bytes_total[5m])))\",\"type\":\"query_range\"}", "size": 0, "aggregation": 3, "title": "πŸ“ˆ Top 5 AKS Nodes by Disk I/O (bytes/sec)", "noDataMessage": "No Prometheus data found.", "timeContextFromParameter": "PrometheusTimeRange", "queryType": 16, "resourceType": "microsoft.monitor/accounts", "crossComponentResources": [ "{AzureMonitorWorkspace}" ], "visualization": "timechart", "chartSettings": { "group": "*", "createOtherGroup": 50, "ySettings": { "numberFormatSettings": { "unit": 36, "options": { "style": "decimal", "useGrouping": true, "minimumFractionDigits": 0, "maximumFractionDigits": 2 } } }, "showLegend": true } }, "customWidth": "50", "conditionalVisibility": { "parameterName": "ClusterFilter", "comparison": "isNotEqualTo", "value": "value::all" }, "name": "cluster-aks-node-disk-chart" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "{\"version\":\"PrometheusQueryProvider/1.0\",\"customEndpoint\":false,\"queryText\":\"topk(5, avg by (cluster, instance) (rate(node_network_receive_bytes_total[5m]) + rate(node_network_transmit_bytes_total[5m])))\",\"type\":\"query_range\"}", "size": 0, "aggregation": 3, "title": "πŸ“ˆ Top 5 AKS Nodes by Network Throughput (bytes/sec)", "noDataMessage": "No Prometheus data found.", "timeContextFromParameter": "PrometheusTimeRange", "queryType": 16, "resourceType": "microsoft.monitor/accounts", "crossComponentResources": [ "{AzureMonitorWorkspace}" ], "visualization": "timechart", "chartSettings": { "group": "*", "createOtherGroup": 50, "ySettings": { "numberFormatSettings": { "unit": 36, "options": { "style": "decimal", "useGrouping": true, "minimumFractionDigits": 0, "maximumFractionDigits": 2 } } }, "showLegend": true } }, "customWidth": "50", "conditionalVisibility": { "parameterName": "ClusterFilter", "comparison": "isNotEqualTo", "value": "value::all" }, "name": "cluster-aks-node-network-chart" } ] }, "conditionalVisibility": { "parameterName": "CapacitySection", "comparison": "isEqualTo", "value": "overview" }, "name": "cap-overview-section" }, { "type": 12, "content": { "version": "NotebookGroup/1.0", "groupType": "editable", "items": [ { "type": 1, "content": { "json": "## 🌍 Multi-Cluster View\r\nMonitor resource exhaustion trends and capacity forecasts across your Azure Local fleet." }, "name": "multi-header-text" }, { "type": 9, "content": { "version": "KqlParameterItem/1.0", "parameters": [ { "id": "forecast-workspace-param", "version": "KqlParameterItem/1.0", "name": "MachinesLogAnalyticsWorkspace", "label": "Log Analytics Workspace", "type": 2, "description": "Select one or more Log Analytics workspaces collecting Azure Local node performance data. Defaults to All.", "isRequired": true, "multiSelect": true, "quote": "'", "delimiter": ",", "query": "resources\r\n| where type == \"microsoft.operationalinsights/workspaces\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n| project value = id, label = name\r\n| order by label asc", "crossComponentResources": [ "{Subscriptions}" ], "typeSettings": { "additionalResourceOptions": [ "value::all" ], "includeAll": true, "showDefault": false }, "defaultValue": "value::all", "value": [ "value::all" ], "queryType": 1, "resourceType": "microsoft.resourcegraph/resources" }, { "id": "forecast-cluster-filter-param", "version": "KqlParameterItem/1.0", "name": "ForecastClusterFilter", "label": "Cluster", "type": 2, "description": "Filter forecast results by cluster name.", "isRequired": true, "multiSelect": true, "quote": "", "delimiter": ",", "query": "resources\r\n| where type == \"microsoft.azurestackhci/clusters\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n| project value = name, label = name\r\n| order by label asc", "crossComponentResources": [ "{Subscriptions}" ], "typeSettings": { "additionalResourceOptions": [ "value::all" ], "showDefault": false }, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "value": [ "value::all" ] }, { "id": "forecast-resource-filter-param", "version": "KqlParameterItem/1.0", "name": "ForecastResourceFilter", "label": "Resource", "type": 2, "description": "Filter forecast results by resource type.", "isRequired": true, "multiSelect": true, "quote": "", "delimiter": ",", "typeSettings": { "additionalResourceOptions": [ "value::all" ], "showDefault": false }, "jsonData": "[{\"value\":\"CPU\",\"label\":\"CPU\"},{\"value\":\"Memory\",\"label\":\"Memory\"},{\"value\":\"Storage\",\"label\":\"Storage\"}]", "value": [ "value::all" ] }, { "id": "forecast-time-range-param", "version": "KqlParameterItem/1.0", "name": "ForecastTimeRange", "label": "Historic Data Time Range", "type": 4, "isRequired": true, "typeSettings": { "selectableValues": [ { "durationMs": 604800000 }, { "durationMs": 1209600000 }, { "durationMs": 2592000000 }, { "durationMs": 5184000000 }, { "durationMs": 7776000000 }, { "durationMs": 15552000000 }, { "durationMs": 31536000000 } ], "allowCustom": true }, "value": { "durationMs": 2592000000 } }, { "id": "warning-threshold-param", "version": "KqlParameterItem/1.0", "name": "WarningThreshold", "label": "Warning Threshold (%)", "type": 2, "description": "Usage percentage at which a node is considered in Warning state.", "isRequired": true, "typeSettings": { "additionalResourceOptions": [], "showDefault": false }, "jsonData": "[{\"value\":\"70\",\"label\":\"70%\"},{\"value\":\"75\",\"label\":\"75%\"},{\"value\":\"80\",\"label\":\"80% (Default)\"},{\"value\":\"85\",\"label\":\"85%\"}]", "value": "80" }, { "id": "critical-threshold-param", "version": "KqlParameterItem/1.0", "name": "CriticalThreshold", "label": "Critical Threshold (%)", "type": 2, "description": "Usage percentage at which a node is considered in Critical state.", "isRequired": true, "typeSettings": { "additionalResourceOptions": [], "showDefault": false }, "jsonData": "[{\"value\":\"85\",\"label\":\"85%\"},{\"value\":\"90\",\"label\":\"90% (Default)\"},{\"value\":\"95\",\"label\":\"95%\"}]", "value": "90" } ], "style": "pills", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces" }, "name": "exhaustion-forecast-filters" }, { "type": 1, "content": { "json": "---\r\n## πŸ“‰ Predictive Resource Exhaustion Forecast by Cluster\r\nProjected days until resource thresholds are reached, aggregated per cluster." }, "name": "text-exhaustion-forecast-header" }, { "type": 1, "content": { "json": "πŸ’‘ **Tip:** Data availability may vary across clusters depending on which performance counters are configured in the Azure Monitoring Agent (AMA) [Data Collection Rule (DCR)](https://learn.microsoft.com/azure/azure-monitor/data-collection/data-collection-rule-overview). If a cluster does not appear in a chart, it may not be collecting that specific counter (e.g., Processor, Memory, Storage or Network).", "style": "upsell" }, "name": "mc-perf-data-tip" }, { "type": 1, "content": { "json": "πŸ’‘ **How this works:** This table analyses historical CPU, Memory and Storage utilisation data from the selected Log Analytics Workspace over the **Historic Data Time Range**. It applies linear regression to calculate the daily rate of change (trend) and estimates how many days until the configurable **Warning** and **Critical** thresholds are reached. A longer time range (e.g. 30+ days) produces more accurate forecasts. 'N/A' means usage is stable or declining β€” no exhaustion is projected.", "style": "info" }, "name": "exhaustion-forecast-tip" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "let trendStart = {ForecastTimeRange:start};\r\nlet trendEnd = {ForecastTimeRange:end};\r\nlet warnPct = todouble({WarningThreshold});\r\nlet critPct = todouble({CriticalThreshold});\r\nlet step = 1d;\r\nlet clusterMap = materialize(\r\n print mapping = dynamic({ClusterRGMap})\r\n | mv-expand mapping to typeof(string)\r\n | extend parts = split(mapping, ':')\r\n | project nodeRG = tolower(tostring(parts[0])), clusterName = tostring(parts[1])\r\n);\r\nlet hciNodes = materialize(Heartbeat\r\n| where TimeGenerated between (trendStart .. trendEnd)\r\n| where OSType == \"Windows\"\r\n| summarize arg_max(TimeGenerated, *) by Computer\r\n| project Computer, nodeRG = tolower(ResourceGroup)\r\n| lookup kind=inner clusterMap on nodeRG\r\n| project Computer, clusterName);\r\nlet cpuData = Perf\r\n| where TimeGenerated between (trendStart .. trendEnd)\r\n| where ObjectName == \"Processor\" and CounterName == \"% Processor Time\" and InstanceName == \"_Total\"\r\n| lookup kind=inner hciNodes on Computer\r\n| summarize AvgValue = avg(CounterValue) by clusterName, bin(TimeGenerated, step)\r\n| make-series Value = avg(AvgValue) default=0 on TimeGenerated from trendStart to trendEnd step step by clusterName\r\n| extend (RSquare, SplitCoeff, Variance, RVariance, LineFit, Baseline) = series_fit_line(Value)\r\n| extend CurrentAvg = round(toreal(series_stats_dynamic(Value).avg), 1)\r\n| extend SlopePerDay = round(SplitCoeff, 3)\r\n| extend Resource = \"CPU\"\r\n| project clusterName, Resource, CurrentAvg, SlopePerDay;\r\nlet memData = Perf\r\n| where TimeGenerated between (trendStart .. trendEnd)\r\n| where ObjectName == \"Memory\" and CounterName == \"% Committed Bytes In Use\"\r\n| lookup kind=inner hciNodes on Computer\r\n| summarize AvgValue = avg(CounterValue) by clusterName, bin(TimeGenerated, step)\r\n| make-series Value = avg(AvgValue) default=0 on TimeGenerated from trendStart to trendEnd step step by clusterName\r\n| extend (RSquare, SplitCoeff, Variance, RVariance, LineFit, Baseline) = series_fit_line(Value)\r\n| extend CurrentAvg = round(toreal(series_stats_dynamic(Value).avg), 1)\r\n| extend SlopePerDay = round(SplitCoeff, 3)\r\n| extend Resource = \"Memory\"\r\n| project clusterName, Resource, CurrentAvg, SlopePerDay;\r\nlet storData = Perf\r\n| where TimeGenerated between (trendStart .. trendEnd)\r\n| where (ObjectName == \"LogicalDisk\" and CounterName in (\"% Free Space\", \"% Used Space\", \"% Disk Space Used\"))\r\n or (ObjectName == \"Cluster CSV File System\" and CounterName in (\"% Used Space\", \"% Free Space\"))\r\n or (ObjectName == \"Cluster Shared Volume\" and CounterName in (\"% Free Space\", \"% Used Space\"))\r\n| where InstanceName != \"_Total\" and InstanceName != \"HarddiskVolume1\"\r\n| lookup kind=inner hciNodes on Computer\r\n| extend UsedPercent = iff(CounterName == \"% Free Space\", 100.0 - CounterValue, CounterValue)\r\n| summarize AvgValue = avg(UsedPercent) by clusterName, bin(TimeGenerated, step)\r\n| make-series Value = avg(AvgValue) default=0 on TimeGenerated from trendStart to trendEnd step step by clusterName\r\n| extend (RSquare, SplitCoeff, Variance, RVariance, LineFit, Baseline) = series_fit_line(Value)\r\n| extend CurrentAvg = round(toreal(series_stats_dynamic(Value).avg), 1)\r\n| extend SlopePerDay = round(SplitCoeff, 3)\r\n| extend Resource = \"Storage\"\r\n| project clusterName, Resource, CurrentAvg, SlopePerDay;\r\nlet insightsStorData = InsightsMetrics\r\n| where TimeGenerated between (trendStart .. trendEnd)\r\n| where Namespace == \"LogicalDisk\" and Name == \"FreeSpacePercentage\"\r\n| extend Computer = tostring(split(_ResourceId, '/')[8])\r\n| lookup kind=inner hciNodes on Computer\r\n| extend UsedPercent = 100.0 - Val\r\n| summarize AvgValue = avg(UsedPercent) by clusterName, bin(TimeGenerated, step)\r\n| make-series Value = avg(AvgValue) default=0 on TimeGenerated from trendStart to trendEnd step step by clusterName\r\n| extend (RSquare, SplitCoeff, Variance, RVariance, LineFit, Baseline) = series_fit_line(Value)\r\n| extend CurrentAvg = round(toreal(series_stats_dynamic(Value).avg), 1)\r\n| extend SlopePerDay = round(SplitCoeff, 3)\r\n| extend Resource = \"Storage\"\r\n| project clusterName, Resource, CurrentAvg, SlopePerDay;\r\nunion cpuData, memData, storData, insightsStorData\r\n| summarize CurrentAvg = max(CurrentAvg), SlopePerDay = max(SlopePerDay) by clusterName, Resource\r\n| extend Trend = case(SlopePerDay > 0.1, \"↑ Rising\", SlopePerDay < -0.1, \"↓ Declining\", \"β†’ Stable\")\r\n| extend DaysToWarning = iff(SlopePerDay > 0 and CurrentAvg < warnPct, round((warnPct - CurrentAvg) / SlopePerDay, 0), iff(CurrentAvg >= warnPct, todouble(0), todouble(-1)))\r\n| extend DaysToCritical = iff(SlopePerDay > 0 and CurrentAvg < critPct, round((critPct - CurrentAvg) / SlopePerDay, 0), iff(CurrentAvg >= critPct, todouble(0), todouble(-1)))\r\n| extend DaysToWarningDisplay = case(DaysToWarning == 0, \"⚠ Already exceeded\", DaysToWarning < 0, \"N/A (stable/declining)\", DaysToWarning >= 365, \"~365+ days\", strcat(\"~\", tostring(toint(DaysToWarning)), \" days\"))\r\n| extend DaysToCriticalDisplay = case(DaysToCritical == 0, \"🚨 Already exceeded\", DaysToCritical < 0, \"N/A (stable/declining)\", DaysToCritical >= 365, \"~365+ days\", strcat(\"~\", tostring(toint(DaysToCritical)), \" days\"))\r\n| extend Status = case(\r\n CurrentAvg >= critPct or (DaysToCritical >= 0 and DaysToCritical <= 14), \"πŸ”΄ Critical\",\r\n CurrentAvg >= warnPct or (DaysToWarning >= 0 and DaysToWarning <= 30), \"🟑 Warning\",\r\n \"🟒 OK\")\r\n| where ('{ForecastResourceFilter}' == 'value::all' or Resource in (split('{ForecastResourceFilter}', ',')))\r\n| where ('{ForecastClusterFilter}' == 'value::all' or clusterName in (split('{ForecastClusterFilter}', ',')))\r\n| project [\"Cluster Name\"] = clusterName, Resource, [\"Current Avg %%\"] = CurrentAvg, Trend, [\"Days to Warning\"] = DaysToWarningDisplay, [\"Days to Critical\"] = DaysToCriticalDisplay, Status\r\n| order by Status asc, Resource asc, [\"Cluster Name\"] asc", "size": 0, "aggregation": 3, "title": "Predictive Resource Exhaustion Forecast by Cluster", "noDataMessage": "No data available. Ensure Azure Stack HCI nodes are reporting Heartbeat and Perf data to the selected workspace.", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", "crossComponentResources": [ "{MachinesLogAnalyticsWorkspace}" ], "visualization": "table", "gridSettings": { "formatters": [ { "columnMatch": "Current Avg %", "formatter": 8, "formatOptions": { "palette": "greenRed", "min": 0, "max": 100 } }, { "columnMatch": "Status", "formatter": 1, "formatOptions": { "customColumnWidthSetting": "120px" } }, { "columnMatch": "Trend", "formatter": 1, "formatOptions": { "customColumnWidthSetting": "120px" } } ], "rowLimit": 6000, "labelSettings": [ { "columnId": "Cluster Name", "label": "Cluster" }, { "columnId": "Days to Warning", "label": "Est. Days to Warning" }, { "columnId": "Days to Critical", "label": "Est. Days to Critical" } ] }, "sortBy": [] }, "name": "node-exhaustion-forecast-table" }, { "type": 1, "content": { "json": "---\r\n### πŸ“Š Cluster-wise Resource Forecast" }, "name": "multi-charts-header" }, { "type": 1, "content": { "json": "πŸ’‘ **Tip:** Select up to 5 specific clusters to compare using the **'Forecast Clusters'** filter (below), or leave as 'All' (defaults to top 5 by resource usage).", "style": "upsell" }, "name": "multi-charts-tip" }, { "type": 1, "content": { "json": "⚠️ **Forecast Disclaimer:** The forecast lines are statistical extrapolations based on historic usage trends using linear regression. They are **not guaranteed predictions** β€” if usage patterns change (e.g., new workloads deployed, workloads removed, or seasonal variation), actual values may differ significantly. Use these forecasts as directional indicators to support capacity planning discussions, not as definitive projections.", "style": "warning" }, "name": "multi-forecast-disclaimer-tip" }, { "type": 9, "content": { "version": "KqlParameterItem/1.0", "crossComponentResources": [ "{Subscriptions}" ], "parameters": [ { "id": "chart-cluster-filter-param", "version": "KqlParameterItem/1.0", "name": "ChartClusterFilter", "label": "Forecast Clusters", "type": 2, "description": "Select up to 5 clusters for the forecast charts. Default (All) = Top 5 by resource usage.", "isRequired": true, "multiSelect": true, "quote": "'", "delimiter": ",", "query": "resources\r\n| where type == \"microsoft.azurestackhci/clusters\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n| project value = tostring(id), label = tostring(name)\r\n| order by label asc", "crossComponentResources": [ "{Subscriptions}" ], "typeSettings": { "additionalResourceOptions": [ "value::all" ], "includeAll": true, "showDefault": false }, "defaultValue": "value::all", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "value": [ "value::all" ] }, { "id": "NodeTrendsTimeRange-mc", "version": "KqlParameterItem/1.0", "name": "NodeTrendsTimeRange", "label": "Historic Data Time Range", "type": 4, "isRequired": true, "typeSettings": { "selectableValues": [ { "durationMs": 604800000 }, { "durationMs": 1209600000 }, { "durationMs": 2592000000 }, { "durationMs": 5184000000 }, { "durationMs": 7776000000 }, { "durationMs": 15552000000 }, { "durationMs": 31536000000 } ], "allowCustom": true }, "value": { "durationMs": 2592000000 } }, { "id": "ForecastHorizonDays-mc", "version": "KqlParameterItem/1.0", "name": "ForecastHorizonDays", "label": "Forecast Horizon", "type": 2, "description": "Days how long the forecast should be", "isRequired": true, "typeSettings": { "additionalResourceOptions": [] }, "jsonData": "[{\"value\":\"15\",\"label\":\"15 Days\"},{\"value\":\"30\",\"label\":\"30 Days (Default)\"},{\"value\":\"60\",\"label\":\"60 days\"},{\"value\":\"90\",\"label\":\"90 Days\"},{\"value\":\"180\",\"label\":\"180 Days\"},{\"value\":\"360\",\"label\":\"360 Days\"}]", "value": "30" } ], "style": "pills", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources" }, "name": "multi-chart-cluster-filter" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "let trendStart = {NodeTrendsTimeRange:start};\r\nlet trendEnd = {NodeTrendsTimeRange:end};\r\nlet horizonDays = toint('{ForecastHorizonDays}');\r\nlet step = 1d;\r\nlet seriesEnd = trendEnd + horizonDays*step;\r\n\r\nlet isAllClusters = \"{ChartClusterFilter}\" contains \"value::all\";\r\n\r\n// seasonality = lookback/2 expressed as #points\r\nlet lookback = trendEnd - trendStart;\r\nlet seasonalityPoints = max_of(2, toint((lookback / 2) / step)); // remove max_of if you want it pure\r\n\r\n// 1) Map node -> cluster, apply ChartClusterFilter\r\nlet NodeToCluster =\r\n Event\r\n | where TimeGenerated between (trendStart - 90d .. trendEnd)\r\n | where EventLog =~ \"Microsoft-Windows-SDDC-Management/Operational\"\r\n | where EventID == 3002\r\n | extend nodeName = tolower(Computer)\r\n | extend x = parse_xml(EventData)\r\n | extend ClusterName_s = tostring(x.DataItem.UserData.EventData[\"ClusterName\"])\r\n | extend ClusterArmId_s = tostring(x.DataItem.UserData.EventData[\"ArmId\"])\r\n | where isnotempty(ClusterArmId_s)\r\n | where isAllClusters or ClusterArmId_s in~ ({ChartClusterFilter})\r\n | summarize arg_max(TimeGenerated, ClusterName_s, ClusterArmId_s) by nodeName\r\n | project nodeName, ClusterName_s, ClusterArmId_s;\r\n\r\n// 2) Daily CPU avg per cluster\r\nlet perClusterPerDay =\r\n Perf\r\n | where TimeGenerated between (trendStart .. trendEnd)\r\n | where ObjectName == \"Processor\"\r\n and CounterName == \"% Processor Time\"\r\n and InstanceName == \"_Total\"\r\n | extend nodeName = tolower(Computer)\r\n | join kind=inner NodeToCluster on nodeName\r\n | summarize CpuPct = avg(todouble(CounterValue)) by ClusterName_s, Day = bin(TimeGenerated, step)\r\n | project ClusterName_s, TimeGenerated = Day, CpuPct;\r\n\r\n// 3) One series per cluster -> forecast per cluster -> expand\r\nlet top10 = perClusterPerDay\r\n | summarize AvgVal = avg(CpuPct) by ClusterName_s\r\n | top 10 by AvgVal desc\r\n | project ClusterName_s;\r\nlet filteredData = perClusterPerDay\r\n | where isAllClusters == false or ClusterName_s in ((top10 | project ClusterName_s));\r\nfilteredData\r\n| make-series CpuPct = avg(CpuPct) default=double(null)\r\n on TimeGenerated\r\n from trendStart to seriesEnd step step\r\n by ClusterName_s\r\n| extend CpuSmooth = series_fill_linear(CpuPct)\r\n| extend (forecast, trend, seasonal, residual) =\r\n series_decompose_forecast(CpuSmooth, horizonDays, seasonalityPoints, \"linefit\")\r\n| mv-expand\r\n TimeGenerated to typeof(datetime),\r\n CpuSmooth to typeof(double),\r\n forecast to typeof(double)\r\n| extend\r\n Actual = iff(TimeGenerated <= trendEnd, CpuSmooth, real(null)),\r\n Forecast = iff(TimeGenerated > trendEnd, forecast, real(null))\r\n| extend\r\n Series = pack_array(strcat(ClusterName_s, \" - Actual\"), strcat(ClusterName_s, \" - Forecast\")),\r\n Value = pack_array(Actual, Forecast)\r\n| mv-expand Series to typeof(string), Value to typeof(double)\r\n| where isnotnull(Value)\r\n| project TimeGenerated, Series, Value\r\n| order by TimeGenerated asc", "size": 0, "aggregation": 3, "title": "Cluster-wise CPU Forecast", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", "crossComponentResources": [ "{MachinesLogAnalyticsWorkspace}" ], "visualization": "linechart", "chartSettings": { "xAxis": "TimeGenerated", "yAxis": [ "Value" ], "group": "Series", "createOtherGroup": null, "showLegend": true, "seriesLabelSettings": [], "ySettings": { "min": 0, "numberFormatSettings": { "unit": 1, "options": { "style": "decimal", "maximumFractionDigits": 1 } } } } }, "customWidth": "50", "name": "mc-cpu-forecast", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "// Multi-cluster Memory Used % Forecast (Actual + Forecast) via Event 3002 mapping\r\n// Preferred: Committed/(Committed+Available)*100\r\n// Fallback: % Committed Bytes In Use (when either counter missing)\r\nlet trendStart = {NodeTrendsTimeRange:start};\r\nlet trendEnd = {NodeTrendsTimeRange:end};\r\nlet horizonDays = toint('{ForecastHorizonDays}');\r\nlet step = 1d;\r\nlet seriesEnd = trendEnd + horizonDays*step;\r\n\r\nlet isAllClusters = \"{ChartClusterFilter}\" contains \"value::all\";\r\n\r\n// seasonality = lookback/2 expressed as #points\r\nlet lookback = trendEnd - trendStart;\r\nlet seasonalityPoints = max_of(2, toint((lookback / 2) / step)); // remove max_of if you want it pure\r\n\r\n// 1) Map node -> cluster, apply ChartClusterFilter\r\nlet NodeToCluster =\r\n Event\r\n | where TimeGenerated between (trendStart - 90d .. trendEnd)\r\n | where EventLog =~ \"Microsoft-Windows-SDDC-Management/Operational\"\r\n | where EventID == 3002\r\n | extend nodeName = tolower(Computer)\r\n | extend x = parse_xml(EventData)\r\n | extend ClusterName_s = tostring(x.DataItem.UserData.EventData[\"ClusterName\"])\r\n | extend ClusterArmId_s = tostring(x.DataItem.UserData.EventData[\"ArmId\"])\r\n | where isnotempty(ClusterArmId_s)\r\n | where isAllClusters or ClusterArmId_s in~ ({ChartClusterFilter})\r\n | summarize arg_max(TimeGenerated, ClusterName_s, ClusterArmId_s) by nodeName\r\n | project nodeName, ClusterName_s, ClusterArmId_s;\r\n\r\n// 2) Daily Memory % per cluster (preferred formula, fallback to % Committed Bytes In Use)\r\nlet perClusterPerDay =\r\n Perf\r\n | where TimeGenerated between (trendStart .. trendEnd)\r\n | where ObjectName == \"Memory\"\r\n | where CounterName in (\"Committed Bytes\", \"Available Bytes\", \"% Committed Bytes In Use\")\r\n | extend nodeName = tolower(Computer)\r\n | join kind=inner NodeToCluster on nodeName\r\n | summarize\r\n CommittedBytes = avgif(todouble(CounterValue), CounterName == \"Committed Bytes\"),\r\n AvailableBytes = avgif(todouble(CounterValue), CounterName == \"Available Bytes\"),\r\n HaveCommitted = countif(CounterName == \"Committed Bytes\"),\r\n HaveAvailable = countif(CounterName == \"Available Bytes\"),\r\n CommitPct = avgif(todouble(CounterValue), CounterName == \"% Committed Bytes In Use\"),\r\n HaveCommitPct = countif(CounterName == \"% Committed Bytes In Use\")\r\n by ClusterName_s, Day = bin(TimeGenerated, step)\r\n | extend Denom = CommittedBytes + AvailableBytes\r\n | extend MemPct =\r\n case(\r\n HaveCommitted > 0 and HaveAvailable > 0 and Denom > 0, (CommittedBytes * 100.0) / Denom,\r\n HaveCommitPct > 0, CommitPct,\r\n real(null)\r\n )\r\n | project ClusterName_s, TimeGenerated = Day, MemPct;\r\n\r\n// 3) One series per cluster -> forecast per cluster -> expand\r\nlet top10 = perClusterPerDay\r\n | summarize AvgVal = avg(MemPct) by ClusterName_s\r\n | top 10 by AvgVal desc\r\n | project ClusterName_s;\r\nlet filteredData = perClusterPerDay\r\n | where isAllClusters == false or ClusterName_s in ((top10 | project ClusterName_s));\r\nfilteredData\r\n| make-series MemPct = avg(MemPct) default=double(null)\r\n on TimeGenerated\r\n from trendStart to seriesEnd step step\r\n by ClusterName_s\r\n| extend MemSmooth = series_fill_linear(MemPct)\r\n| extend (forecast, trend, seasonal, residual) =\r\n series_decompose_forecast(MemSmooth, horizonDays, seasonalityPoints, \"linefit\")\r\n| mv-expand\r\n TimeGenerated to typeof(datetime),\r\n MemSmooth to typeof(double),\r\n forecast to typeof(double)\r\n| extend\r\n Actual = iff(TimeGenerated <= trendEnd, MemSmooth, real(null)),\r\n Forecast = iff(TimeGenerated > trendEnd, forecast, real(null))\r\n| extend\r\n Series = pack_array(strcat(ClusterName_s, \" - Actual\"), strcat(ClusterName_s, \" - Forecast\")),\r\n Value = pack_array(Actual, Forecast)\r\n| mv-expand Series to typeof(string), Value to typeof(double)\r\n| where isnotnull(Value)\r\n| project TimeGenerated, Series, Value\r\n| order by TimeGenerated asc", "size": 0, "aggregation": 3, "title": "Cluster-wise Memory Forecast", "timeContext": { "durationMs": 86400000 }, "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", "crossComponentResources": [ "{MachinesLogAnalyticsWorkspace}" ], "visualization": "linechart", "chartSettings": { "seriesLabelSettings": [], "xAxis": "TimeGenerated", "yAxis": [ "Value" ], "group": "Series", "showLegend": true, "ySettings": { "min": 0, "numberFormatSettings": { "unit": 1, "options": { "style": "decimal", "maximumFractionDigits": 1 } } } } }, "customWidth": "50", "name": "mc-mem-forecast", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "let trendStart = {NodeTrendsTimeRange:start};\r\nlet trendEnd = {NodeTrendsTimeRange:end};\r\nlet horizonDays = toint('{ForecastHorizonDays}');\r\nlet step = 12h;\r\nlet seriesEnd = trendEnd + horizonDays*step;\r\n\r\n// seasonality = lookback/2 expressed as #points\r\nlet lookback = trendEnd - trendStart;\r\nlet seasonalityPoints = max_of(2, toint((lookback / 2) / step));\r\n\r\n// Detect \"All\"\r\nlet isAllClusters = \"{ChartClusterFilter}\" contains \"value::all\";\r\n\r\n// 1) Per cluster per day Storage Remaining %\r\nlet perClusterPerDay =\r\n Event\r\n | where TimeGenerated between (trendStart .. trendEnd)\r\n | where EventLog =~ \"Microsoft-Windows-SDDC-Management/Operational\"\r\n | where EventID == 3002\r\n | extend x = parse_xml(EventData)\r\n | extend ClusterName = tostring(x.DataItem.UserData.EventData[\"ClusterName\"])\r\n | extend ArmId = tostring(x.DataItem.UserData.EventData[\"ArmId\"])\r\n | where isnotempty(ArmId)\r\n | where isAllClusters or ArmId in~ ({ChartClusterFilter})\r\n | extend volumes = parse_json(RenderedDescription).VolumeList\r\n | mv-expand volumes\r\n | extend TotalCap = tolong(volumes.m_Size)\r\n | extend UsedCap = tolong(volumes.m_SizeUsed)\r\n | summarize TotalBytes=sum(TotalCap), UsedBytes=sum(UsedCap)\r\n by ClusterName, ArmId, Day = bin(TimeGenerated, step)\r\n | extend RemainingPct =\r\n iif(TotalBytes > 0,\r\n 100.0 * todouble(TotalBytes - UsedBytes) / todouble(TotalBytes),\r\n real(null))\r\n | project ClusterName, TimeGenerated = Day, RemainingPct;\r\n\r\n// 2) Forecast per cluster\r\nlet top10 = perClusterPerDay\r\n | summarize AvgVal = avg(RemainingPct) by ClusterName\r\n | top 10 by AvgVal asc\r\n | project ClusterName;\r\nlet filteredData = perClusterPerDay\r\n | where isAllClusters == false or ClusterName in ((top10 | project ClusterName));\r\nfilteredData\r\n| make-series RemainingPct = avg(RemainingPct) default=double(null)\r\n on TimeGenerated\r\n from trendStart to seriesEnd step step\r\n by ClusterName\r\n| extend Smooth = series_fill_linear(RemainingPct)\r\n| extend (forecast, trend, seasonal, residual) =\r\n series_decompose_forecast(Smooth, horizonDays, seasonalityPoints, \"linefit\")\r\n| mv-expand\r\n TimeGenerated to typeof(datetime),\r\n Smooth to typeof(double),\r\n forecast to typeof(double)\r\n| extend\r\n Actual = iff(TimeGenerated <= trendEnd, Smooth, real(null)),\r\n Forecast = iff(TimeGenerated > trendEnd, forecast, real(null))\r\n| extend\r\n Series = pack_array(\r\n strcat(ClusterName, \" - Actual\"),\r\n strcat(ClusterName, \" - Forecast\")\r\n ),\r\n Value = pack_array(Actual, Forecast)\r\n| mv-expand Series to typeof(string), Value to typeof(double)\r\n| where isnotnull(Value)\r\n| project TimeGenerated, Series, Value\r\n| order by TimeGenerated asc", "size": 0, "aggregation": 3, "title": "Storage Remaining (%) β€” Cluster Forecast", "timeContext": { "durationMs": 86400000 }, "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", "crossComponentResources": [ "{MachinesLogAnalyticsWorkspace}" ], "visualization": "timechart", "chartSettings": { "seriesLabelSettings": [], "xAxis": "TimeGenerated", "yAxis": [ "Value" ], "group": "Series", "showLegend": true, "ySettings": { "min": 0, "numberFormatSettings": { "unit": 1, "options": { "style": "decimal", "maximumFractionDigits": 1 } }, "max": 100 } } }, "customWidth": "50", "name": "mc-stor-pct-forecast", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "// Storage Available (TB) - Actual vs Forecast (per cluster, multi-select + All)\r\nlet trendStart = {NodeTrendsTimeRange:start};\r\nlet trendEnd = {NodeTrendsTimeRange:end};\r\nlet horizonDays = toint('{ForecastHorizonDays}');\r\nlet step = 1d;\r\nlet seriesEnd = trendEnd + horizonDays*step;\r\n\r\n// seasonality = lookback/2 expressed as #points\r\nlet lookback = trendEnd - trendStart;\r\nlet seasonalityPoints = max_of(2, toint((lookback / 2) / step));\r\n\r\n// Detect \"All\"\r\nlet isAllClusters = \"{ChartClusterFilter}\" contains \"value::all\";\r\n\r\n// 1) Per cluster per day Available TB\r\nlet perClusterPerDay =\r\n Event\r\n | where TimeGenerated between (trendStart .. trendEnd)\r\n | where EventLog =~ \"Microsoft-Windows-SDDC-Management/Operational\"\r\n | where EventID == 3002\r\n | extend x = parse_xml(tostring(EventData))\r\n | extend ClusterName = tostring(x.DataItem.UserData.EventData[\"ClusterName\"])\r\n | extend ArmId = tostring(x.DataItem.UserData.EventData[\"ArmId\"])\r\n | where isnotempty(ArmId)\r\n | where isAllClusters or ArmId in~ ({ChartClusterFilter})\r\n | extend Day = bin(TimeGenerated, step)\r\n | summarize arg_max(TimeGenerated, RenderedDescription) by ClusterName, ArmId, Day\r\n | extend rdj = parse_json(RenderedDescription)\r\n | mv-expand vol = rdj.VolumeList\r\n | extend TotalCap = tolong(vol.m_Size)\r\n | extend UsedCap = tolong(vol.m_SizeUsed)\r\n | summarize TotalBytes=sum(TotalCap), UsedBytes=sum(UsedCap)\r\n by ClusterName, ArmId, Day\r\n | extend AvailableBytes = TotalBytes - UsedBytes\r\n | extend AvailableTB = todouble(AvailableBytes) / 1e12\r\n | project ClusterName, TimeGenerated = Day, AvailableTB;\r\n\r\n// 2) Forecast per cluster (CPU-style output)\r\nlet top10 = perClusterPerDay\r\n | summarize AvgVal = avg(AvailableTB) by ClusterName\r\n | top 10 by AvgVal asc\r\n | project ClusterName;\r\nlet filteredData = perClusterPerDay\r\n | where isAllClusters == false or ClusterName in ((top10 | project ClusterName));\r\nfilteredData\r\n| make-series AvailableTB = avg(AvailableTB) default=double(null)\r\n on TimeGenerated\r\n from trendStart to seriesEnd step step\r\n by ClusterName\r\n| extend Smooth = series_fill_linear(AvailableTB)\r\n| extend (forecast, trend, seasonal, residual) =\r\n series_decompose_forecast(Smooth, horizonDays, seasonalityPoints, \"linefit\")\r\n| mv-expand\r\n TimeGenerated to typeof(datetime),\r\n Smooth to typeof(double),\r\n forecast to typeof(double)\r\n| extend\r\n Actual = iff(TimeGenerated <= trendEnd, Smooth, real(null)),\r\n Forecast = iff(TimeGenerated > trendEnd, forecast, real(null))\r\n| extend\r\n Series = pack_array(\r\n strcat(ClusterName, \" - Actual\"),\r\n strcat(ClusterName, \" - Forecast\")\r\n ),\r\n Value = pack_array(Actual, Forecast)\r\n| mv-expand Series to typeof(string), Value to typeof(double)\r\n| where isnotnull(Value)\r\n| project TimeGenerated, Series, Value\r\n| order by TimeGenerated asc", "size": 0, "aggregation": 3, "title": "Storage Available (TB) β€” Cluster Forecast", "timeContext": { "durationMs": 86400000 }, "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", "crossComponentResources": [ "{MachinesLogAnalyticsWorkspace}" ], "visualization": "timechart", "chartSettings": { "seriesLabelSettings": [], "xAxis": "TimeGenerated", "yAxis": [ "Value" ], "group": "Series", "showLegend": true, "ySettings": { "min": 0, "numberFormatSettings": { "unit": 0, "options": { "style": "decimal", "maximumFractionDigits": 2 } } } } }, "customWidth": "48", "name": "mc-stor-tb-forecast" }, { "type": 1, "content": { "json": "### πŸ’Ύ Storage & Network Performance β€” For Selected Clusters" }, "name": "mc-perf-charts-header" }, { "type": 9, "content": { "version": "KqlParameterItem/1.0", "parameters": [ { "id": "node-trends-time-range-mc-perf", "version": "KqlParameterItem/1.0", "name": "NodeTrendsTimeRange", "label": "Historic Time Range", "type": 4, "description": "Time range for the Storage & Network performance charts below.", "isRequired": true, "typeSettings": { "selectableValues": [ { "durationMs": 1800000 }, { "durationMs": 3600000 }, { "durationMs": 14400000 }, { "durationMs": 43200000 }, { "durationMs": 86400000 }, { "durationMs": 259200000 }, { "durationMs": 604800000 }, { "durationMs": 1209600000 }, { "durationMs": 2592000000 } ], "allowCustom": true }, "value": { "durationMs": 259200000 } } ], "style": "pills", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources" }, "name": "mc-perf-time-range-param" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "let trendStart = {NodeTrendsTimeRange:start};\nlet trendEnd = {NodeTrendsTimeRange:end};\nlet step = 1d;\nlet isAllClusters = \"{ChartClusterFilter}\" contains \"value::all\";\n\nlet NodeToCluster = materialize(\n Event\n | where TimeGenerated between (trendStart - 90d .. trendEnd)\n | where EventLog =~ \"Microsoft-Windows-SDDC-Management/Operational\"\n | where EventID == 3002\n | extend nodeName = tolower(Computer)\n | extend x = parse_xml(EventData)\n | extend ClusterName_s = tostring(x.DataItem.UserData.EventData[\"ClusterName\"])\n | extend ClusterArmId_s = tostring(x.DataItem.UserData.EventData[\"ArmId\"])\n | where isnotempty(ClusterArmId_s)\n | where isAllClusters or ClusterArmId_s in~ ({ChartClusterFilter})\n | summarize arg_max(TimeGenerated, ClusterName_s, ClusterArmId_s) by nodeName\n | project nodeName, clusterName = ClusterName_s\n);\n\nPerf\n| where TimeGenerated between (trendStart .. trendEnd)\n| where ObjectName == \"LogicalDisk\"\n and CounterName in (\"Avg. Disk sec/Read\", \"Avg. Disk sec/Write\")\n and InstanceName == \"_Total\"\n| extend LatencyMs = CounterValue * 1000.0\n| extend nodeName = tolower(Computer)\n| lookup kind=inner NodeToCluster on nodeName\n| summarize [\"Avg Latency (ms)\"] = round(avg(LatencyMs), 2) by bin(TimeGenerated, step), clusterName\n| order by TimeGenerated asc", "size": 0, "aggregation": 3, "title": "Storage Latency (ms) β€” Selected Clusters", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", "crossComponentResources": [ "{MachinesLogAnalyticsWorkspace}" ], "visualization": "linechart", "chartSettings": { "xAxis": "TimeGenerated", "yAxis": [ "Avg Latency (ms)" ], "group": "clusterName", "createOtherGroup": null, "showLegend": false, "seriesLabelSettings": [], "ySettings": { "min": 0, "numberFormatSettings": { "unit": 0, "options": { "style": "decimal", "maximumFractionDigits": 2 } } } } }, "customWidth": "33", "name": "mc-storage-latency", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "let trendStart = {NodeTrendsTimeRange:start};\nlet trendEnd = {NodeTrendsTimeRange:end};\nlet trendStartBuffer = {NodeTrendsTimeRange:start} - 90d;\nlet step = 1d;\nlet isAllClusters = \"{ChartClusterFilter}\" contains \"value::all\";\n\nlet NodeToCluster = materialize(\n Event\n | where TimeGenerated between (trendStartBuffer .. trendEnd)\n | where EventLog =~ \"Microsoft-Windows-SDDC-Management/Operational\"\n | where EventID == 3002\n | extend nodeName = tolower(Computer)\n | extend x = parse_xml(EventData)\n | extend ClusterName_s = tostring(x.DataItem.UserData.EventData[\"ClusterName\"])\n | extend ClusterArmId_s = tostring(x.DataItem.UserData.EventData[\"ArmId\"])\n | where isnotempty(ClusterArmId_s)\n | where isAllClusters or ClusterArmId_s in~ ({ChartClusterFilter})\n | summarize arg_max(TimeGenerated, ClusterName_s, ClusterArmId_s) by nodeName\n | project nodeName, clusterName = ClusterName_s\n);\n\nlet perfIOPS = Perf\n| where TimeGenerated between (trendStart .. trendEnd)\n| where ObjectName == \"LogicalDisk\"\n and CounterName == \"Disk Transfers/sec\"\n and InstanceName == \"_Total\"\n| extend nodeName = tolower(Computer)\n| project TimeGenerated, nodeName, IOPS = CounterValue;\n\nlet insightsIOPS = InsightsMetrics\n| where TimeGenerated between (trendStart .. trendEnd)\n| where Namespace == \"LogicalDisk\"\n and Name == \"TransfersPerSecond\"\n| extend nodeName = tolower(tostring(split(_ResourceId, \"/\")[8]))\n| project TimeGenerated, nodeName, IOPS = Val;\n\nunion perfIOPS, insightsIOPS\n| lookup kind=inner NodeToCluster on nodeName\n| summarize [\"Avg IOPS\"] = round(avg(IOPS), 0) by bin(TimeGenerated, step), clusterName\n| order by TimeGenerated asc", "size": 0, "aggregation": 3, "title": "Storage IOPS β€” Selected Clusters", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", "crossComponentResources": [ "{MachinesLogAnalyticsWorkspace}" ], "visualization": "linechart", "chartSettings": { "xAxis": "TimeGenerated", "yAxis": [ "Avg IOPS" ], "group": "clusterName", "createOtherGroup": null, "showLegend": false, "seriesLabelSettings": [], "ySettings": { "min": 0, "numberFormatSettings": { "unit": 0, "options": { "style": "decimal", "maximumFractionDigits": 0 } } } } }, "customWidth": "33", "name": "mc-storage-iops", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "let trendStart = {NodeTrendsTimeRange:start};\nlet trendEnd = {NodeTrendsTimeRange:end};\nlet step = 1d;\nlet isAllClusters = \"{ChartClusterFilter}\" contains \"value::all\";\nlet NodeToCluster = materialize(\n Event\n | where TimeGenerated between (trendStart - 90d .. trendEnd)\n | where EventLog =~ \"Microsoft-Windows-SDDC-Management/Operational\"\n | where EventID == 3002\n | extend nodeName = tolower(Computer)\n | extend x = parse_xml(EventData)\n | extend ClusterName_s = tostring(x.DataItem.UserData.EventData[\"ClusterName\"])\n | extend ClusterArmId_s = tostring(x.DataItem.UserData.EventData[\"ArmId\"])\n | where isnotempty(ClusterArmId_s)\n | where isAllClusters or ClusterArmId_s in~ ({ChartClusterFilter})\n | summarize arg_max(TimeGenerated, ClusterName_s, ClusterArmId_s) by nodeName\n | project nodeName, clusterName = ClusterName_s\n);\nlet perfNet = Perf\n| where TimeGenerated between (trendStart .. trendEnd)\n| where ObjectName in (\"Network Adapter\", \"Network Interface\") and CounterName == \"Bytes Total/sec\"\n| where InstanceName != \"_Total\"\n| extend nodeName = tolower(Computer)\n| summarize BytesPerSec = sum(CounterValue) by TimeGenerated, nodeName;\nlet insightsNet = InsightsMetrics\n| where TimeGenerated between (trendStart .. trendEnd)\n| where Namespace == \"Network\" and Name in (\"WriteBytesPerSecond\", \"ReadBytesPerSecond\")\n| extend nodeName = tolower(tostring(split(_ResourceId, \"/\")[8]))\n| summarize BytesPerSec = sum(Val) by TimeGenerated, nodeName;\nunion perfNet, insightsNet\n| lookup kind=inner NodeToCluster on nodeName\n| summarize [\"Avg Throughput (MB/s)\"] = round(avg(BytesPerSec) / 1048576.0, 1) by bin(TimeGenerated, step), clusterName\n| order by TimeGenerated asc", "size": 0, "aggregation": 3, "title": "Network Throughput (MB/s) β€” Selected Clusters", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", "crossComponentResources": [ "{MachinesLogAnalyticsWorkspace}" ], "visualization": "linechart", "chartSettings": { "xAxis": "TimeGenerated", "yAxis": [ "Avg Throughput (MB/s)" ], "group": "clusterName", "createOtherGroup": null, "showLegend": false, "seriesLabelSettings": [], "ySettings": { "min": 0, "numberFormatSettings": { "unit": 0, "options": { "style": "decimal", "maximumFractionDigits": 1 } } } } }, "customWidth": "33", "name": "mc-network-throughput", "styleSettings": { "margin": "5px", "padding": "5px" } } ] }, "conditionalVisibility": { "parameterName": "CapacitySection", "comparison": "isEqualTo", "value": "multi" }, "name": "cap-multi-section" }, { "type": 12, "content": { "version": "NotebookGroup/1.0", "groupType": "editable", "items": [ { "type": 1, "content": { "json": "## πŸ” Single Cluster View\r\nDrill into capacity, storage, forecasts and workloads for a specific Azure Local cluster." }, "name": "single-header-text" }, { "type": 1, "content": { "json": "πŸ’‘ **Tip:** Select an Azure Local cluster using the **'Select Cluster'** drop-down below. Optionally, select a specific **Log Analytics Workspace** if your clusters report to different workspaces.", "style": "upsell" }, "name": "single-cluster-tip" }, { "type": 9, "content": { "version": "KqlParameterItem/1.0", "crossComponentResources": [ "{Subscriptions}" ], "parameters": [ { "id": "machines-log-analytics-workspace-sc", "version": "KqlParameterItem/1.0", "name": "MachinesLogAnalyticsWorkspace", "label": "Log Analytics Workspace", "type": 2, "description": "Select one or more Log Analytics workspaces collecting Azure Local node performance data. Defaults to All.", "isRequired": true, "multiSelect": true, "quote": "'", "delimiter": ",", "query": "resources\r\n| where type == \"microsoft.operationalinsights/workspaces\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| project value = id, label = name\r\n| order by label asc", "crossComponentResources": [ "{Subscriptions}" ], "typeSettings": { "additionalResourceOptions": [ "value::all" ], "includeAll": true, "showDefault": false }, "defaultValue": "value::all", "value": [ "value::all" ], "queryType": 1, "resourceType": "microsoft.resourcegraph/resources" }, { "id": "single-cluster-armid", "version": "KqlParameterItem/1.0", "name": "SingleCluster", "label": "Select Cluster", "type": 2, "isRequired": true, "query": "resources\r\n| where type == \"microsoft.azurestackhci/clusters\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n| project value = tostring(id), label = name\r\n| order by label asc", "crossComponentResources": [ "{Subscriptions}" ], "typeSettings": { "additionalResourceOptions": [], "showDefault": false }, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources" }, { "id": "NodeTrendsTimeRange-sc", "version": "KqlParameterItem/1.0", "name": "NodeTrendsTimeRange", "label": "Historic Data Time Range", "type": 4, "isRequired": true, "typeSettings": { "selectableValues": [ { "durationMs": 604800000 }, { "durationMs": 1209600000 }, { "durationMs": 2592000000 }, { "durationMs": 5184000000 }, { "durationMs": 7776000000 }, { "durationMs": 15552000000 }, { "durationMs": 31536000000 } ], "allowCustom": true }, "value": { "durationMs": 2592000000 } }, { "id": "ForecastHorizonDays-sc", "version": "KqlParameterItem/1.0", "name": "ForecastHorizonDays", "label": "Forecast Horizon", "type": 2, "description": "Days how long the forecast should be", "isRequired": true, "typeSettings": { "additionalResourceOptions": [] }, "jsonData": "[{\"value\":\"15\",\"label\":\"15 Days\"},{\"value\":\"30\",\"label\":\"30 Days (Default)\"},{\"value\":\"60\",\"label\":\"60 days\"},{\"value\":\"90\",\"label\":\"90 Days\"},{\"value\":\"180\",\"label\":\"180 Days\"},{\"value\":\"360\",\"label\":\"360 Days\"}]", "value": "30" } ], "style": "pills", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources" }, "name": "single-cluster-params" }, { "type": 1, "content": { "json": "### πŸ—οΈ Cluster Capacity\r\nPhysical hardware resources for: **{SingleCluster:label}**" }, "name": "sc-fleet-header" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.hybridcompute/machines\"\r\n| where properties.cloudMetadata.provider == \"AzSHCI\"\r\n| where kind != \"HCI\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterName = name, clusterRG = resourceGroup\r\n) on $left.resourceGroup == $right.clusterRG\r\n| where clusterName =~ tostring(split('{SingleCluster}', '/')[8])\r\n| extend logicalCoreCount = toint(properties.detectedProperties.logicalCoreCount)\r\n| extend memoryGB = toint(properties.detectedProperties.totalPhysicalMemoryInGigabytes)\r\n| summarize Clusters = dcount(clusterName)\r\n| extend Label = 'Clusters'", "size": 0, "aggregation": 3, "title": "Clusters", "noDataMessage": "β€”", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "tiles", "tileSettings": { "titleContent": { "columnMatch": "Label", "formatter": 1 }, "leftContent": { "columnMatch": "Clusters", "formatter": 12, "formatOptions": { "palette": "blue" } }, "showBorder": true, "size": "auto" }, "chartSettings": { "showLegend": false, "seriesLabelSettings": [] } }, "customWidth": "10", "name": "sc-fleet-clusters", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.hybridcompute/machines\"\r\n| where properties.cloudMetadata.provider == \"AzSHCI\"\r\n| where kind != \"HCI\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterName = name, clusterRG = resourceGroup\r\n) on $left.resourceGroup == $right.clusterRG\r\n| where clusterName =~ tostring(split('{SingleCluster}', '/')[8])\r\n| extend logicalCoreCount = toint(properties.detectedProperties.logicalCoreCount)\r\n| extend memoryGB = toint(properties.detectedProperties.totalPhysicalMemoryInGigabytes)\r\n| summarize Nodes = count()\r\n| extend Label = 'Nodes'", "size": 0, "aggregation": 3, "title": "Nodes", "noDataMessage": "β€”", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "tiles", "tileSettings": { "titleContent": { "columnMatch": "Label", "formatter": 1 }, "leftContent": { "columnMatch": "Nodes", "formatter": 12, "formatOptions": { "palette": "blue" } }, "showBorder": true, "size": "auto" }, "chartSettings": { "showLegend": false, "seriesLabelSettings": [] } }, "customWidth": "10", "name": "sc-fleet-nodes", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.hybridcompute/machines\"\r\n| where properties.cloudMetadata.provider == \"AzSHCI\"\r\n| where kind != \"HCI\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterName = name, clusterResourceGroup = resourceGroup, physicalCoresPerNode = toint(properties.reportedProperties.nodes[0].coreCount), osDisplayVersion = tostring(properties.reportedProperties.nodes[0].osDisplayVersion)\r\n) on $left.resourceGroup == $right.clusterResourceGroup\r\n| where clusterName =~ tostring(split('{SingleCluster}', '/')[8])\r\n| extend nodeName = name\r\n| extend status = tostring(properties.status)\r\n| extend logicalCores = toint(properties.detectedProperties.logicalCoreCount)\r\n| extend memoryGiB = toint(properties.detectedProperties.totalPhysicalMemoryInGigabytes)\r\n| extend osVersion = replace_string(tostring(properties.osVersion), '10.0.', '')\r\n| extend osEdition = coalesce(osDisplayVersion, iff(osVersion startswith '26100', '24H2', iff(osVersion startswith '25398', '23H2', iff(osVersion startswith '20349', '21H2', ''))))\r\n| extend physicalCores = iff(isnotempty(physicalCoresPerNode), physicalCoresPerNode, toint(logicalCores / 2))\r\n| extend machineLink = strcat('https://portal.azure.com/#@/resource', id)\r\n| project nodeName, machineLink, clusterName, status, osEdition, osVersion, physicalCores, logicalCores, memoryGiB\r\n| order by clusterName asc, nodeName asc", "size": 0, "aggregation": 3, "title": "Node Hardware Summary", "noDataMessage": "No Azure Stack HCI nodes found. Verify your subscription and resource group filters.", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "table", "gridSettings": { "formatters": [ { "columnMatch": "nodeName", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkColumn": "machineLink" } }, { "columnMatch": "machineLink", "formatter": 5 }, { "columnMatch": "status", "formatter": 18, "formatOptions": { "thresholdsOptions": "icons", "thresholdsGrid": [ { "operator": "==", "thresholdValue": "Connected", "representation": "success", "text": "{0}{1}" }, { "operator": "Default", "representation": "critical", "text": "{0}{1}" } ] } }, { "columnMatch": "physicalCores", "formatter": 1, "formatOptions": { "customColumnWidthSetting": "120px" } }, { "columnMatch": "logicalCores", "formatter": 1, "formatOptions": { "customColumnWidthSetting": "120px" } }, { "columnMatch": "memoryGiB", "formatter": 1, "formatOptions": { "customColumnWidthSetting": "140px" }, "numberFormat": { "unit": 5, "options": { "style": "decimal", "minimumFractionDigits": 0, "maximumFractionDigits": 0 } } } ], "labelSettings": [ { "columnId": "nodeName", "label": "Machine" }, { "columnId": "clusterName", "label": "Cluster" }, { "columnId": "status", "label": "Status" }, { "columnId": "osEdition", "label": "OS Edition" }, { "columnId": "osVersion", "label": "OS Version" }, { "columnId": "physicalCores", "label": "Physical Cores" }, { "columnId": "logicalCores", "label": "Logical Cores" }, { "columnId": "memoryGiB", "label": "Physical Memory" } ] }, "chartSettings": { "showLegend": false, "seriesLabelSettings": [] } }, "customWidth": "80", "name": "sc-fleet-hardware", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 1, "content": { "json": "### πŸ—οΈ Cluster: {SingleCluster:label} β€” Capacity & Performance of Physical Machines" }, "name": "sc-perf-charts-header" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "let trendStart = {NodeTrendsTimeRange:start};\nlet trendEnd = {NodeTrendsTimeRange:end};\nlet step = 1d;\n\nlet NodeToCluster = materialize(\n Event\n | where TimeGenerated between (trendStart - 90d .. trendEnd)\n | where EventLog =~ \"Microsoft-Windows-SDDC-Management/Operational\"\n | where EventID == 3002\n | extend nodeName = tolower(Computer)\n | extend x = parse_xml(EventData)\n | extend ClusterArmId = tostring(x.DataItem.UserData.EventData[\"ArmId\"])\n | where isnotempty(ClusterArmId)\n | where ClusterArmId =~ '{SingleCluster}'\n | summarize arg_max(TimeGenerated, ClusterArmId) by nodeName\n | project nodeName\n);\n\nPerf\n| where TimeGenerated between (trendStart .. trendEnd)\n| where ObjectName == \"Processor\" and CounterName == \"% Processor Time\" and InstanceName == \"_Total\"\n| extend nodeName = tolower(Computer)\n| lookup kind=inner NodeToCluster on nodeName\n| summarize [\"Avg Usage %\"] = round(avg(CounterValue), 1) by bin(TimeGenerated, step), nodeName\n| project TimeGenerated, Computer = nodeName, [\"Avg Usage %\"]\n| order by TimeGenerated asc", "size": 0, "aggregation": 3, "title": "CPU Usage (%) β€” By Machine", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", "crossComponentResources": [ "{MachinesLogAnalyticsWorkspace}" ], "visualization": "linechart", "chartSettings": { "xAxis": "TimeGenerated", "yAxis": [ "Avg Usage %" ], "group": "Computer", "createOtherGroup": null, "showLegend": true, "ySettings": { "min": 0, "max": 100, "numberFormatSettings": { "unit": 1, "options": { "style": "decimal", "maximumFractionDigits": 1 } } } } }, "customWidth": "33", "name": "sc-cpu-usage-machine", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "let trendStart = {NodeTrendsTimeRange:start};\nlet trendEnd = {NodeTrendsTimeRange:end};\nlet step = 1d;\n\nlet NodeToCluster = materialize(\n Event\n | where TimeGenerated between (trendStart - 90d .. trendEnd)\n | where EventLog =~ \"Microsoft-Windows-SDDC-Management/Operational\"\n | where EventID == 3002\n | extend nodeName = tolower(Computer)\n | extend x = parse_xml(EventData)\n | extend ClusterArmId = tostring(x.DataItem.UserData.EventData[\"ArmId\"])\n | where isnotempty(ClusterArmId)\n | where ClusterArmId =~ '{SingleCluster}'\n | summarize arg_max(TimeGenerated, ClusterArmId) by nodeName\n | project nodeName\n);\n\nPerf\n| where TimeGenerated between (trendStart .. trendEnd)\n| where ObjectName == \"Memory\" and CounterName == \"% Committed Bytes In Use\"\n| extend nodeName = tolower(Computer)\n| lookup kind=inner NodeToCluster on nodeName\n| summarize [\"Avg Usage %\"] = round(avg(CounterValue), 1) by bin(TimeGenerated, step), nodeName\n| project TimeGenerated, Computer = nodeName, [\"Avg Usage %\"]\n| order by TimeGenerated asc", "size": 0, "aggregation": 3, "title": "Memory Usage (%) β€” By Machine", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", "crossComponentResources": [ "{MachinesLogAnalyticsWorkspace}" ], "visualization": "linechart", "chartSettings": { "xAxis": "TimeGenerated", "yAxis": [ "Avg Usage %" ], "group": "Computer", "createOtherGroup": null, "showLegend": true, "ySettings": { "min": 0, "max": 100, "numberFormatSettings": { "unit": 1, "options": { "style": "decimal", "maximumFractionDigits": 1 } } } } }, "customWidth": "33", "name": "sc-memory-usage-machine", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "let trendStart = {NodeTrendsTimeRange:start};\nlet trendEnd = {NodeTrendsTimeRange:end};\nlet step = 1d;\n\nlet NodeToCluster = materialize(\n Event\n | where TimeGenerated between (trendStart - 90d .. trendEnd)\n | where EventLog =~ \"Microsoft-Windows-SDDC-Management/Operational\"\n | where EventID == 3002\n | extend nodeName = tolower(Computer)\n | extend x = parse_xml(EventData)\n | extend ClusterArmId = tostring(x.DataItem.UserData.EventData[\"ArmId\"])\n | where isnotempty(ClusterArmId)\n | where ClusterArmId =~ '{SingleCluster}'\n | summarize arg_max(TimeGenerated, ClusterArmId) by nodeName\n | project nodeName\n);\n\nlet perfStorage = Perf\n| where TimeGenerated between (trendStart .. trendEnd)\n| where (ObjectName == \"LogicalDisk\" and CounterName in (\"% Free Space\", \"% Used Space\", \"% Disk Space Used\"))\n or (ObjectName == \"Cluster CSV File System\" and CounterName in (\"% Used Space\", \"% Free Space\"))\n or (ObjectName == \"Cluster Shared Volume\" and CounterName in (\"% Free Space\", \"% Used Space\"))\n| where InstanceName != \"_Total\" and InstanceName != \"HarddiskVolume1\"\n| extend nodeName = tolower(Computer)\n| extend UsedPercent = iff(CounterName == \"% Free Space\", 100.0 - CounterValue, CounterValue)\n| project TimeGenerated, nodeName, UsedPercent;\n\nlet insightsStorage = InsightsMetrics\n| where TimeGenerated between (trendStart .. trendEnd)\n| where Namespace == \"LogicalDisk\" and Name == \"FreeSpacePercentage\"\n| extend nodeName = tolower(tostring(split(_ResourceId, \"/\")[8]))\n| extend UsedPercent = 100.0 - Val\n| project TimeGenerated, nodeName, UsedPercent;\n\nunion perfStorage, insightsStorage\n| lookup kind=inner NodeToCluster on nodeName\n| summarize [\"Avg Usage %\"] = round(avg(UsedPercent), 1) by bin(TimeGenerated, step), nodeName\n| project TimeGenerated, Computer = nodeName, [\"Avg Usage %\"]\n| order by TimeGenerated asc", "size": 0, "aggregation": 3, "title": "Storage Usage (%) β€” By Machine", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", "crossComponentResources": [ "{MachinesLogAnalyticsWorkspace}" ], "visualization": "linechart", "chartSettings": { "xAxis": "TimeGenerated", "yAxis": [ "Avg Usage %" ], "group": "Computer", "createOtherGroup": null, "showLegend": true, "ySettings": { "min": 0, "max": 100, "numberFormatSettings": { "unit": 1, "options": { "style": "decimal", "maximumFractionDigits": 1 } } } } }, "customWidth": "33", "name": "sc-storage-usage-machine", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "let trendStart = {NodeTrendsTimeRange:start};\nlet trendEnd = {NodeTrendsTimeRange:end};\nlet step = 1d;\n\nlet NodeToCluster = materialize(\n Event\n | where TimeGenerated between (trendStart - 90d .. trendEnd)\n | where EventLog =~ \"Microsoft-Windows-SDDC-Management/Operational\"\n | where EventID == 3002\n | extend nodeName = tolower(Computer)\n | extend x = parse_xml(EventData)\n | extend ClusterArmId = tostring(x.DataItem.UserData.EventData[\"ArmId\"])\n | where isnotempty(ClusterArmId)\n | where ClusterArmId =~ '{SingleCluster}'\n | summarize arg_max(TimeGenerated, ClusterArmId) by nodeName\n | project nodeName\n);\n\n// Latency only exists in Perf (ReadLatencyMs absent from InsightsMetrics)\nlet perfLatency = Perf\n| where TimeGenerated between (trendStart .. trendEnd)\n| where ObjectName == \"LogicalDisk\"\n and CounterName in (\"Avg. Disk sec/Read\", \"Avg. Disk sec/Write\")\n and InstanceName == \"_Total\" // ← only _Total exists in your DCR\n| extend LatencyMs = CounterValue * 1000.0\n| extend nodeName = tolower(Computer)\n| project TimeGenerated, nodeName, LatencyMs;\n\nperfLatency\n| lookup kind=inner NodeToCluster on nodeName\n| summarize [\"Avg Latency (ms)\"] = round(avg(LatencyMs), 2) by bin(TimeGenerated, step), nodeName\n| project TimeGenerated, Computer = nodeName, [\"Avg Latency (ms)\"] // ← rename to Computer\n| order by TimeGenerated asc", "size": 0, "aggregation": 3, "title": "Storage Latency (ms) β€” By Machine", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", "crossComponentResources": [ "{MachinesLogAnalyticsWorkspace}" ], "visualization": "linechart", "chartSettings": { "xAxis": "TimeGenerated", "yAxis": [ "Avg Latency (ms)" ], "group": "Computer", "createOtherGroup": null, "ySettings": { "numberFormatSettings": { "unit": 0, "options": { "style": "decimal", "maximumFractionDigits": 2 } }, "min": 0 } } }, "customWidth": "33", "name": "sc-storage-latency-node", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "let trendStart = {NodeTrendsTimeRange:start};\nlet trendEnd = {NodeTrendsTimeRange:end};\nlet trendStartBuffer = {NodeTrendsTimeRange:start} - 30d;\nlet step = 1d;\n\nlet NodeToCluster = materialize(\n Event\n | where TimeGenerated between (trendStartBuffer .. trendEnd)\n | where EventLog =~ \"Microsoft-Windows-SDDC-Management/Operational\"\n | where EventID == 3002\n | extend nodeName = tolower(Computer)\n | extend x = parse_xml(EventData)\n | extend ClusterArmId = tostring(x.DataItem.UserData.EventData[\"ArmId\"])\n | where isnotempty(ClusterArmId)\n | where ClusterArmId =~ '{SingleCluster}'\n | summarize arg_max(TimeGenerated, ClusterArmId) by nodeName\n | project nodeName\n);\n\nlet perfIOPS = Perf\n| where TimeGenerated between (trendStart .. trendEnd)\n| where ObjectName == \"LogicalDisk\"\n and CounterName == \"Disk Transfers/sec\"\n and InstanceName == \"_Total\"\n| extend nodeName = tolower(Computer)\n| project TimeGenerated, nodeName, IOPS = CounterValue;\n\nlet insightsIOPS = InsightsMetrics\n| where TimeGenerated between (trendStart .. trendEnd)\n| where Namespace == \"LogicalDisk\"\n and Name == \"TransfersPerSecond\"\n| extend nodeName = tolower(tostring(split(_ResourceId, \"/\")[8]))\n| project TimeGenerated, nodeName, IOPS = Val;\n\nunion perfIOPS, insightsIOPS\n| lookup kind=inner NodeToCluster on nodeName\n| summarize [\"Avg IOPS\"] = round(avg(IOPS), 0) by bin(TimeGenerated, step), nodeName\n| order by TimeGenerated asc", "size": 0, "aggregation": 3, "title": "Storage IOPS β€” By Machine", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", "crossComponentResources": [ "{MachinesLogAnalyticsWorkspace}" ], "visualization": "linechart", "chartSettings": { "xAxis": "TimeGenerated", "yAxis": [ "Avg IOPS" ], "group": "nodeName", "createOtherGroup": null, "showLegend": false, "seriesLabelSettings": [], "ySettings": { "min": 0, "numberFormatSettings": { "unit": 0, "options": { "style": "decimal", "maximumFractionDigits": 0 } } } } }, "customWidth": "33", "name": "sc-storage-iops-node", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "let trendStart = {NodeTrendsTimeRange:start};\nlet trendEnd = {NodeTrendsTimeRange:end};\nlet step = 1d;\nlet NodeToCluster = materialize(\n Event\n | where TimeGenerated between (trendStart - 30d .. trendEnd)\n | where EventLog =~ \"Microsoft-Windows-SDDC-Management/Operational\"\n | where EventID == 3002\n | extend nodeName = tolower(Computer)\n | extend x = parse_xml(EventData)\n | extend ClusterArmId = tostring(x.DataItem.UserData.EventData[\"ArmId\"])\n | where isnotempty(ClusterArmId)\n | where ClusterArmId =~ '{SingleCluster}'\n | summarize arg_max(TimeGenerated, ClusterArmId) by nodeName\n | project nodeName\n);\nlet perfNet = Perf\n| where TimeGenerated between (trendStart .. trendEnd)\n| where ObjectName in (\"Network Adapter\", \"Network Interface\") and CounterName == \"Bytes Total/sec\"\n| where InstanceName != \"_Total\"\n| extend nodeName = tolower(Computer)\n| summarize BytesPerSec = sum(CounterValue) by TimeGenerated, nodeName;\nlet insightsNet = InsightsMetrics\n| where TimeGenerated between (trendStart .. trendEnd)\n| where Namespace == \"Network\" and Name in (\"WriteBytesPerSecond\", \"ReadBytesPerSecond\")\n| extend nodeName = tolower(tostring(split(_ResourceId, \"/\")[8]))\n| summarize BytesPerSec = sum(Val) by TimeGenerated, nodeName;\nunion perfNet, insightsNet\n| lookup kind=inner NodeToCluster on nodeName\n| summarize [\"Avg Throughput (MB/s)\"] = round(avg(BytesPerSec) / 1048576.0, 1) by bin(TimeGenerated, step), nodeName\n| order by TimeGenerated asc", "size": 0, "aggregation": 3, "title": "Network Throughput (MB/s) β€” By Machine", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", "crossComponentResources": [ "{MachinesLogAnalyticsWorkspace}" ], "visualization": "linechart", "chartSettings": { "xAxis": "TimeGenerated", "yAxis": [ "Avg Throughput (MB/s)" ], "group": "nodeName", "createOtherGroup": null, "showLegend": false, "seriesLabelSettings": [], "ySettings": { "min": 0, "numberFormatSettings": { "unit": 0, "options": { "style": "decimal", "maximumFractionDigits": 1 } } } } }, "customWidth": "33", "name": "sc-network-throughput-node", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 1, "content": { "json": "### πŸ’Ύ Cluster: {SingleCluster:label} β€” Storage Volume Usage and Forecast" }, "name": "sc-storage-bar-header" }, { "type": 1, "content": { "json": "πŸ’‘ **Tip:** The chart below uses [Storage Path](https://learn.microsoft.com/en-us/azure-stack/hci/manage/create-storage-path) resources in Azure to report volume usage data." }, "conditionalVisibility": { "parameterName": "ClusterFilter", "comparison": "isNotEqualTo", "value": "value::all" }, "name": "text-storage-paths-tip" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.azurestackhci/storagecontainers\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| extend customLocKey = tolower(trim_end(\"/\", tostring(extendedLocation.name)))\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.extendedlocation/customlocations\"\r\n | where tostring(properties.hostResourceId) contains \"Microsoft.ResourceConnector/appliances\"\r\n | extend arcBridgeRG = tolower(tostring(split(tostring(properties.hostResourceId), '/')[4]))\r\n | project customLocKey = tolower(trim_end(\"/\", id)), arcBridgeRG\r\n) on customLocKey\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where name =~ tostring(split('{SingleCluster}', '/')[8])\r\n | where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n | project clusterName = name, clusterRG = tolower(resourceGroup)\r\n) on $left.arcBridgeRG == $right.clusterRG\r\n| extend pathName = name\r\n| extend totalGB = round(toreal(properties.status.containerSizeMB) / 1024, 0)\r\n| extend availableGB = round(toreal(properties.status.availableSizeMB) / 1024, 0)\r\n| extend usedGB = totalGB - availableGB\r\n| project pathName, UsedGB = usedGB, AvailableGB = availableGB\r\n| order by pathName asc", "size": 0, "aggregation": 3, "title": "Storage Volume Usage (GB) β€” {SingleCluster:label}", "noDataMessage": "No storage containers found for this cluster.", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "barchart", "chartSettings": { "xAxis": "pathName", "yAxis": [ "UsedGB", "AvailableGB" ], "group": null, "createOtherGroup": null, "showDataPointsCount": true, "seriesLabelSettings": [], "ySettings": { "numberFormatSettings": { "unit": 0 } }, "showLegend": false } }, "customWidth": "50", "conditionalVisibility": { "parameterName": "ClusterFilter", "comparison": "isNotEqualTo", "value": "value::all" }, "name": "sc-storage-paths-chart", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "let trendStart = {NodeTrendsTimeRange:start};\r\nlet trendEnd = {NodeTrendsTimeRange:end};\r\nlet horizonDays = toint('{ForecastHorizonDays}');\r\nlet step = 1d;\r\nlet seriesEnd = trendEnd + horizonDays*step;\r\n\r\n// seasonality = lookback/2 expressed as #points\r\nlet lookback = trendEnd - trendStart;\r\nlet seasonalityPoints = max_of(2, toint((lookback / 2) / step));\r\n\r\n// 1) Daily Used% per volume (SingleCluster)\r\nlet perVolumePerDay =\r\n Event\r\n | where TimeGenerated between (trendStart .. trendEnd)\r\n | where EventLog =~ \"Microsoft-Windows-SDDC-Management/Operational\"\r\n | where EventID == 3002\r\n | extend x = parse_xml(tostring(EventData))\r\n | extend ClusterArmId_s = tostring(x.DataItem.UserData.EventData[\"ArmId\"])\r\n | where isnotempty(ClusterArmId_s) and ClusterArmId_s =~ \"{SingleCluster}\"\r\n | extend rdj = parse_json(RenderedDescription)\r\n | mv-expand vol = rdj.VolumeList\r\n | extend VolumeLabel = tostring(vol.m_Label)\r\n | extend TotalBytes = tolong(vol.m_Size)\r\n | extend UsedBytes = tolong(vol.m_SizeUsed)\r\n | where isnotempty(VolumeLabel) and TotalBytes > 0\n | where VolumeLabel !contains \"ClusterPerformanceHistory\"\r\n | summarize TotalBytes = any(TotalBytes), UsedBytes = any(UsedBytes)\r\n by VolumeLabel, Day = bin(TimeGenerated, step)\r\n | extend UsedPct = 100.0 * todouble(UsedBytes) / todouble(TotalBytes)\r\n | project VolumeLabel, TimeGenerated = Day, UsedPct;\r\n\r\n// 2) One series per volume -> forecast per volume -> expand (CPU style)\r\nperVolumePerDay\r\n| make-series UsedPct = avg(UsedPct) default=double(null)\r\n on TimeGenerated\r\n from trendStart to seriesEnd step step\r\n by VolumeLabel\r\n| extend UsedSmooth = series_fill_linear(UsedPct)\r\n| extend (forecast, trend, seasonal, residual) =\r\n series_decompose_forecast(UsedSmooth, horizonDays, seasonalityPoints, \"linefit\")\r\n| mv-expand\r\n TimeGenerated to typeof(datetime),\r\n UsedSmooth to typeof(double),\r\n forecast to typeof(double)\r\n| extend\r\n Actual = iff(TimeGenerated <= trendEnd, UsedSmooth, real(null)),\r\n Forecast = iff(TimeGenerated > trendEnd, forecast, real(null))\r\n| extend\r\n Series = pack_array(strcat(VolumeLabel, \" - Actual\"), strcat(VolumeLabel, \" - Forecast\")),\r\n Value = pack_array(Actual, Forecast)\r\n| mv-expand Series to typeof(string), Value to typeof(double)\r\n| where isnotnull(Value)\r\n| project TimeGenerated, Series, Value\r\n| order by TimeGenerated asc", "size": 0, "aggregation": 3, "title": "Volume Usage (%) - Single cluster", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", "crossComponentResources": [ "{MachinesLogAnalyticsWorkspace}" ], "visualization": "timechart", "chartSettings": { "xAxis": "TimeGenerated", "yAxis": [ "Value" ], "group": "Series", "createOtherGroup": null, "showLegend": false, "seriesLabelSettings": [] } }, "customWidth": "50", "name": "single-cluster-volume-usage", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 1, "content": { "json": "### πŸ“Š Storage Pool Trends & Forecast" }, "name": "sc-stor-forecast-header" }, { "type": 1, "content": { "json": "⚠️ **Forecast Disclaimer:** The forecast lines are statistical extrapolations based on historic usage trends using linear regression. They are **not guaranteed predictions** β€” if usage patterns change (e.g., new workloads deployed, workloads removed, or seasonal variation), actual values may differ significantly. Use these forecasts as directional indicators to support capacity planning discussions, not as definitive projections.", "style": "warning" }, "name": "sc-stor-forecast-disclaimer-tip" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "// Storage Available (%) - Actual vs Forecast (Single cluster)\r\nlet trendStart = {NodeTrendsTimeRange:start};\r\nlet trendEnd = {NodeTrendsTimeRange:end};\r\nlet horizonDays = toint('{ForecastHorizonDays}');\r\nlet step = 1d;\r\nlet seriesEnd = trendEnd + horizonDays*step;\r\n\r\n// seasonality = lookback/2 expressed as #points\r\nlet lookback = trendEnd - trendStart;\r\nlet seasonalityPoints = max_of(2, toint((lookback / 2) / step));\r\n\r\nlet perDay =\r\nEvent\r\n| where TimeGenerated between (trendStart .. trendEnd)\r\n| where EventLog =~ \"Microsoft-Windows-SDDC-Management/Operational\"\r\n| where EventID == 3002\r\n| extend x = parse_xml(tostring(EventData))\r\n| extend ArmId = tostring(x.DataItem.UserData.EventData[\"ArmId\"])\r\n| where isnotempty(ArmId)\r\n| where ArmId =~ \"{SingleCluster}\"\r\n| extend rdj = parse_json(RenderedDescription)\r\n| mv-expand vol = rdj.VolumeList\r\n| extend TotalCap = tolong(vol.m_Size)\r\n| extend UsedCap = tolong(vol.m_SizeUsed)\r\n| summarize TotalBytes=sum(TotalCap), UsedBytes=sum(UsedCap) by ArmId, Day = bin(TimeGenerated, step)\r\n| extend AvailableBytes = TotalBytes - UsedBytes\r\n| extend AvailablePct =\r\n iif(TotalBytes > 0, 100.0 * todouble(AvailableBytes) / todouble(TotalBytes), real(null))\r\n| project TimeGenerated = Day, AvailablePct;\r\n\r\n// Forecast\r\nperDay\r\n| make-series AvgValue = avg(AvailablePct) default=double(null)\r\n on TimeGenerated\r\n from trendStart to seriesEnd step step\r\n| extend AvgValueSmooth = series_fill_linear(AvgValue)\r\n| extend (forecast, trend, seasonal, residual) =\r\n series_decompose_forecast(AvgValueSmooth, horizonDays, seasonalityPoints, \"linefit\")\r\n| mv-expand\r\n TimeGenerated to typeof(datetime),\r\n AvgValueSmooth to typeof(double),\r\n forecast to typeof(double),\r\n trend to typeof(double)\r\n| extend\r\n Actual = iff(TimeGenerated <= trendEnd, AvgValueSmooth, real(null)),\r\n Projection = iff(TimeGenerated > trendEnd, forecast, real(null))\r\n| extend\r\n Series = pack_array(\"Actual\",\"Forecast\"),\r\n Value = pack_array(Actual, Projection)\r\n| mv-expand Series to typeof(string), Value to typeof(double)\r\n| where isnotnull(Value)\r\n| project TimeGenerated, Series, Value\r\n| order by TimeGenerated asc", "size": 0, "aggregation": 3, "title": "Storage Pool Remaining (%) - Single cluster (Actual vs Forecast)", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", "crossComponentResources": [ "{MachinesLogAnalyticsWorkspace}" ], "visualization": "linechart", "sortBy": [], "tileSettings": { "showBorder": false }, "graphSettings": { "type": 0 }, "chartSettings": { "xAxis": "TimeGenerated", "yAxis": [ "Value" ], "group": "Series", "seriesLabelKey": "Series", "showLegend": false, "seriesLabelSettings": [], "ySettings": { "min": 0, "max": 100 } }, "mapSettings": { "locInfo": "AzureResource", "locInfoColumn": "ArmId" } }, "customWidth": "50", "name": "single-cluster-storage-pool-trend", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "// Storage Available (TB) - Actual vs Forecast (ClusterFilter multi-select + includeAll)\r\nlet trendStart = {NodeTrendsTimeRange:start};\r\nlet trendEnd = {NodeTrendsTimeRange:end};\r\nlet horizonDays = toint('{ForecastHorizonDays}');\r\nlet step = 1d;\r\nlet seriesEnd = trendEnd + horizonDays*step;\r\n\r\n// seasonality = lookback/2 expressed as #points\r\nlet lookback = trendEnd - trendStart;\r\nlet seasonalityPoints = max_of(2, toint((lookback / 2) / step)); // remove max_of if you want it pure\r\n\r\n// Detect \"All\" selection (value::all injected by parameter includeAll)\r\nlet isAllClusters = false; // single cluster view\r\n\r\nlet perDay =\r\nEvent\r\n| where TimeGenerated between (trendStart .. trendEnd)\r\n| where EventLog =~ \"Microsoft-Windows-SDDC-Management/Operational\"\r\n| where EventID == 3002\r\n| extend x = parse_xml(EventData)\r\n| extend ArmId = tostring(x.DataItem.UserData.EventData[\"ArmId\"])\r\n| where isnotempty(ArmId)\r\n| where ArmId =~ '{SingleCluster}'\r\n| extend volumes = parse_json(RenderedDescription).VolumeList\r\n| mv-expand volumes\r\n| extend TotalCap = tolong(volumes.m_Size)\r\n| extend UsedCap = tolong(volumes.m_SizeUsed)\r\n| summarize TotalBytes=sum(TotalCap), UsedBytes=sum(UsedCap) by ArmId, TimeGenerated\r\n| extend AvailableBytes = TotalBytes - UsedBytes\r\n| extend Day = bin(TimeGenerated, step)\r\n// one point per cluster per day (use latest reading that day)\r\n| summarize arg_max(TimeGenerated, AvailableBytes) by ArmId, Day\r\n// sum across clusters for that day (fleet or selected set)\r\n| summarize AvailableBytes = sum(AvailableBytes) by Day\r\n| extend AvailableTB = todouble(AvailableBytes) / 1e12\r\n| project TimeGenerated = Day, AvailableTB;\r\n\r\nperDay\r\n| make-series AvgValue = avg(AvailableTB) default=double(null)\r\n on TimeGenerated\r\n from trendStart to seriesEnd step step\r\n| extend AvgValueSmooth = series_fill_linear(AvgValue)\r\n| extend (forecast, trend, seasonal, residual) =\r\n series_decompose_forecast(AvgValueSmooth, horizonDays, seasonalityPoints, \"linefit\")\r\n| mv-expand\r\n TimeGenerated to typeof(datetime),\r\n AvgValueSmooth to typeof(double),\r\n forecast to typeof(double),\r\n trend to typeof(double)\r\n| extend\r\n Actual = iff(TimeGenerated <= trendEnd, AvgValueSmooth, real(null)),\r\n Projection = iff(TimeGenerated > trendEnd, forecast, real(null))\r\n| extend\r\n Series = pack_array(\"Actual\",\"Forecast\"),\r\n Value = pack_array(Actual, Projection)\r\n| mv-expand Series to typeof(string), Value to typeof(double)\r\n| where isnotnull(Value)\r\n| project TimeGenerated, Series, Value\r\n| order by TimeGenerated asc", "size": 0, "aggregation": 3, "title": "Storage Pool Remaining (TB) - Single cluster (Actual vs Forecast)", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", "crossComponentResources": [ "{MachinesLogAnalyticsWorkspace}" ], "visualization": "linechart", "sortBy": [], "tileSettings": { "showBorder": false }, "graphSettings": { "type": 0 }, "chartSettings": { "xAxis": "TimeGenerated", "yAxis": [ "Value" ], "group": "Series", "seriesLabelKey": "Series", "showLegend": false, "seriesLabelSettings": [] }, "mapSettings": { "locInfo": "AzureResource", "locInfoColumn": "ArmId" } }, "customWidth": "50", "name": "single-cluster-storage-pool-trend - Copy", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 1, "content": { "json": "### βš™οΈ Compute Trends & Forecast" }, "name": "sc-compute-forecast-header" }, { "type": 1, "content": { "json": "⚠️ **Forecast Disclaimer:** The forecast lines are statistical extrapolations based on historic usage trends using linear regression. They are **not guaranteed predictions** β€” if usage patterns change (e.g., new workloads deployed, workloads removed, or seasonal variation), actual values may differ significantly. Use these forecasts as directional indicators to support capacity planning discussions, not as definitive projections.", "style": "warning" }, "name": "sc-compute-forecast-disclaimer-tip" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "// CPU % Forecast - Multi-cluster ClusterFilter (multi-select + All) via Event 3002 ArmId mapping\r\nlet trendStart = {NodeTrendsTimeRange:start};\r\nlet trendEnd = {NodeTrendsTimeRange:end};\r\nlet lookback = trendEnd - trendStart;\r\nlet horizonDays = toint('{ForecastHorizonDays}'); // set this to 30 in workbook if you want \"future=30d\"\r\nlet step = 24h;\r\nlet seriesEnd = trendEnd + horizonDays*step;\r\n\r\n\r\n// Detect \"All\" selection\r\nlet isAllClusters = false; // single cluster view\r\n\r\n\r\n// 1) Build Node -> Cluster mapping using Event 3002, applying ClusterFilter\r\nlet NodeToCluster =\r\n Event\r\n | where TimeGenerated between (trendStart - 30d .. trendEnd) // buffer for stability\r\n | where EventLog =~ \"Microsoft-Windows-SDDC-Management/Operational\"\r\n | where EventID == 3002\r\n | extend x = parse_xml(EventData)\r\n | extend ClusterArmId = tostring(x.DataItem.UserData.EventData[\"ArmId\"])\r\n | where isnotempty(ClusterArmId)\r\n | where ClusterArmId =~ '{SingleCluster}'\r\n | summarize ClusterArmId = arg_max(TimeGenerated, ClusterArmId).ClusterArmId by Computer;\r\n\r\n\r\n// 2) Pull Perf (CPU) for nodes belonging to selected clusters, then forecast on fleet/selection aggregate\r\nPerf\r\n| where TimeGenerated between (trendStart .. trendEnd)\r\n| where ObjectName == \"Processor\"\r\n and CounterName == \"% Processor Time\"\r\n and InstanceName == \"_Total\"\r\n| join kind=inner NodeToCluster on Computer\r\n| summarize AvgValue = avg(todouble(CounterValue)) by bin(TimeGenerated, step)\r\n| make-series AvgValue = avg(AvgValue) default=double(null)\r\n on TimeGenerated\r\n from trendStart to seriesEnd step step\r\n| extend AvgValueSmooth = series_fill_linear(AvgValue)\r\n| extend seasonality = toint((lookback / 2) / step) // matches your lookback upon 2 idea\r\n| extend (forecast, trend, seasonal, residual) =\r\n series_decompose_forecast(AvgValueSmooth, horizonDays, seasonality, \"linefit\")\r\n| mv-expand\r\n TimeGenerated to typeof(datetime),\r\n AvgValueSmooth to typeof(double),\r\n forecast to typeof(double)\r\n| extend\r\n Actual = iff(TimeGenerated <= trendEnd, AvgValueSmooth, real(null)),\r\n Forecast = iff(TimeGenerated > trendEnd, forecast, real(null))\r\n| extend\r\n Series = pack_array(\"Actual\",\"Forecast\"),\r\n Value = pack_array(Actual, Forecast)\r\n| mv-expand Series to typeof(string), Value to typeof(double)\r\n| where isnotnull(Value)\r\n| project TimeGenerated, Series, Value\r\n| order by TimeGenerated asc", "size": 0, "aggregation": 3, "title": "CPU Usage (%) - Single cluster (Actual vs Forecast)", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", "crossComponentResources": [ "{MachinesLogAnalyticsWorkspace}" ], "visualization": "linechart", "chartSettings": { "xAxis": "TimeGenerated", "yAxis": [ "Value" ], "group": "Series", "seriesLabelKey": "Series", "showLegend": false, "seriesLabelSettings": [], "ySettings": { "min": 0 } } }, "customWidth": "50", "name": "single-cluster-cpu-trend", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "// Memory % Forecast - Multi-cluster ClusterFilter (multi-select + All) via Event 3002 ArmId mapping\r\nlet trendStart = {NodeTrendsTimeRange:start};\r\nlet trendEnd = {NodeTrendsTimeRange:end};\r\nlet horizonDays = toint('{ForecastHorizonDays}');\r\nlet step = 1d;\r\nlet seriesEnd = trendEnd + horizonDays*step;\r\n\r\n// seasonality = lookback/2, expressed in number of points\r\nlet lookback = trendEnd - trendStart;\r\nlet seasonalityPoints = max_of(2, toint((lookback / 2) / step));\r\n\r\n// Detect \"All\" selection\r\nlet isAllClusters = false; // single cluster view\r\n\r\n// 1) Build Node -> Cluster mapping using Event 3002, applying ClusterFilter\r\nlet NodeToCluster =\r\n Event\r\n | where TimeGenerated between (trendStart - 30d .. trendEnd) // buffer for stability\r\n | where EventLog =~ \"Microsoft-Windows-SDDC-Management/Operational\"\r\n | where EventID == 3002\r\n | extend x = parse_xml(EventData)\r\n | extend ClusterArmId = tostring(x.DataItem.UserData.EventData[\"ArmId\"])\r\n | where isnotempty(ClusterArmId)\r\n | where ClusterArmId =~ '{SingleCluster}'\r\n | summarize ClusterArmId = arg_max(TimeGenerated, ClusterArmId).ClusterArmId by Computer;\r\n\r\n// 2) Pull Perf (Memory) for nodes belonging to selected clusters, then forecast on fleet/selection aggregate\r\nPerf\r\n| where TimeGenerated between (trendStart .. trendEnd)\r\n| where ObjectName == \"Memory\"\r\n and CounterName == \"% Committed Bytes In Use\"\r\n| join kind=inner NodeToCluster on Computer\r\n| summarize AvgValue = avg(todouble(CounterValue)) by bin(TimeGenerated, step)\r\n| make-series AvgValue = avg(AvgValue) default=double(null)\r\n on TimeGenerated\r\n from trendStart to seriesEnd step step\r\n| extend AvgValueSmooth = series_fill_linear(AvgValue)\r\n| extend (forecast, trend, seasonal, residual) =\r\n series_decompose_forecast(AvgValueSmooth, horizonDays, seasonalityPoints, \"linefit\")\r\n| mv-expand\r\n TimeGenerated to typeof(datetime),\r\n AvgValueSmooth to typeof(double),\r\n forecast to typeof(double)\r\n| extend\r\n Actual = iff(TimeGenerated <= trendEnd, AvgValueSmooth, real(null)),\r\n Forecast = iff(TimeGenerated > trendEnd, forecast, real(null))\r\n| extend\r\n Series = pack_array(\"Actual\",\"Forecast\"),\r\n Value = pack_array(Actual, Forecast)\r\n| mv-expand Series to typeof(string), Value to typeof(double)\r\n| where isnotnull(Value)\r\n| project TimeGenerated, Series, Value\r\n| order by TimeGenerated asc", "size": 0, "aggregation": 3, "title": "Memory Usage (%) - Single cluster (Actual vs Forecast)", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", "crossComponentResources": [ "{MachinesLogAnalyticsWorkspace}" ], "visualization": "linechart", "chartSettings": { "xAxis": "TimeGenerated", "yAxis": [ "Value" ], "group": "Series", "seriesLabelKey": "Series", "showLegend": false, "seriesLabelSettings": [], "ySettings": { "min": 0 } } }, "customWidth": "50", "name": "single-cluster-memory-trend", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "// VM drilldown - no let statements (ARG constraint)\r\nresources\r\n| where type == \"microsoft.hybridcompute/machines\"\r\n| where kind == \"HCI\"\r\n| extend machineId = tolower(id)\r\n| join kind=inner (\r\n extensibilityresources\r\n | where type == \"microsoft.azurestackhci/virtualmachineinstances\"\r\n | extend customLocKey = tolower(trim_end(\"/\", tostring(extendedLocation.name)))\r\n | extend machineId = tolower(substring(id, 0, indexof(id, \"/providers/Microsoft.AzureStackHCI\")))\r\n | project machineId, customLocKey\r\n) on machineId\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.extendedlocation/customlocations\"\r\n | where tostring(properties.hostResourceId) contains \"Microsoft.ResourceConnector/appliances\"\r\n | extend arcBridgeRG = tolower(tostring(split(tostring(properties.hostResourceId), '/')[4]))\r\n | project customLocKey = tolower(trim_end(\"/\", id)), arcBridgeRG\r\n) on customLocKey\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where name =~ tostring(split('{SingleCluster}', '/')[8])\r\n | where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n | project hciClusterRG = tolower(resourceGroup)\r\n) on $left.arcBridgeRG == $right.hciClusterRG\r\n| extend vmName = name\r\n| extend status = tostring(properties.status)\r\n| extend osSku = tostring(properties.osSku)\r\n| extend osVersion = tostring(properties.osVersion)\r\n| extend vmLink = strcat('https://portal.azure.com/#@/resource', id)\r\n| project vmName, vmLink, clusterName = tostring(split('{SingleCluster}', '/')[8]), status, osSku, osVersion, resourceGroup, subscriptionId\r\n| order by clusterName asc, vmName asc", "size": 0, "aggregation": 3, "noDataMessage": "No VMs found on this cluster.", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "table", "gridSettings": { "formatters": [ { "columnMatch": "vmName", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkLabel": "", "linkIsContextBlade": false, "linkColumn": "vmLink" } }, { "columnMatch": "vmLink", "formatter": 5 }, { "columnMatch": "status", "formatter": 18, "formatOptions": { "thresholdsOptions": "icons", "thresholdsGrid": [ { "operator": "==", "thresholdValue": "Connected", "representation": "success", "text": "{0}{1}" }, { "operator": "Default", "representation": "critical", "text": "{0}{1}" } ] } } ], "labelSettings": [ { "columnId": "vmName", "label": "VM Name" }, { "columnId": "clusterName", "label": "Cluster" }, { "columnId": "status", "label": "Status" }, { "columnId": "osSku", "label": "OS" }, { "columnId": "osVersion", "label": "OS Version" }, { "columnId": "resourceGroup", "label": "Resource Group" } ] }, "chartSettings": { "showLegend": false, "seriesLabelSettings": [] } }, "conditionalVisibility": { "parameterName": "neverVisible", "comparison": "isEqualTo", "value": "alwaysHidden" }, "name": "sc-vms-arg-data" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "let perfData = Perf\r\n| where TimeGenerated {NodeTrendsTimeRange}\r\n| where (ObjectName == 'Processor' and CounterName == '% Processor Time' and InstanceName == '_Total')\r\n or (ObjectName == 'Memory' and CounterName in ('% Committed Bytes In Use', '% Used Memory'))\r\n| project Computer, ObjectName, CounterValue;\r\nlet insightsCPU = InsightsMetrics\r\n| where TimeGenerated {NodeTrendsTimeRange}\r\n| where Namespace == 'Processor' and Name == 'UtilizationPercentage'\r\n| extend Computer = tostring(split(_ResourceId, '/')[8])\r\n| project Computer, ObjectName = 'Processor', CounterValue = Val;\r\nunion perfData, insightsCPU\r\n| summarize AvgCPU = round(avgif(CounterValue, ObjectName == 'Processor'), 1),\r\n MaxCPU = round(maxif(CounterValue, ObjectName == 'Processor'), 1),\r\n AvgMemory = round(avgif(CounterValue, ObjectName == 'Memory'), 1),\r\n MaxMemory = round(maxif(CounterValue, ObjectName == 'Memory'), 1)\r\n by Computer\r\n| project vmName = Computer, AvgCPU, MaxCPU, AvgMemory, MaxMemory", "size": 0, "aggregation": 3, "timeContextFromParameter": "NodeTrendsTimeRange", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", "crossComponentResources": [ "{MachinesLogAnalyticsWorkspace}" ], "chartSettings": { "showLegend": false, "seriesLabelSettings": [] } }, "conditionalVisibility": { "parameterName": "neverVisible", "comparison": "isEqualTo", "value": "alwaysHidden" }, "name": "sc-vms-perf-data" }, { "type": 1, "content": { "json": "## πŸ“¦ Workloads on Cluster: {SingleCluster:label}" }, "name": "sc-workloads-header" }, { "type": 1, "content": { "json": "### πŸ–₯️ Azure Local VMs on: {SingleCluster:label}" }, "name": "sc-vms-title" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "{\"version\":\"Merge/1.0\",\"merges\":[{\"id\":\"vm-with-perf\",\"mergeType\":\"leftouter\",\"leftTable\":\"sc-vms-arg-data\",\"rightTable\":\"sc-vms-perf-data\",\"leftColumn\":\"vmName\",\"rightColumn\":\"vmName\"}],\"projectRename\":[{\"originalName\":\"[sc-vms-arg-data].vmName\",\"mergedName\":\"vmName\",\"fromId\":\"vm-with-perf\"},{\"originalName\":\"[sc-vms-arg-data].vmLink\",\"mergedName\":\"vmLink\",\"fromId\":\"vm-with-perf\"},{\"originalName\":\"[sc-vms-arg-data].clusterName\",\"mergedName\":\"clusterName\",\"fromId\":\"vm-with-perf\"},{\"originalName\":\"[sc-vms-arg-data].status\",\"mergedName\":\"status\",\"fromId\":\"vm-with-perf\"},{\"originalName\":\"[sc-vms-arg-data].osSku\",\"mergedName\":\"osSku\",\"fromId\":\"vm-with-perf\"},{\"originalName\":\"[sc-vms-arg-data].osVersion\",\"mergedName\":\"osVersion\",\"fromId\":\"vm-with-perf\"},{\"originalName\":\"[sc-vms-perf-data].AvgCPU\",\"mergedName\":\"AvgCPU\",\"fromId\":\"vm-with-perf\"},{\"originalName\":\"[sc-vms-perf-data].MaxCPU\",\"mergedName\":\"MaxCPU\",\"fromId\":\"vm-with-perf\"},{\"originalName\":\"[sc-vms-perf-data].AvgMemory\",\"mergedName\":\"AvgMemory\",\"fromId\":\"vm-with-perf\"},{\"originalName\":\"[sc-vms-perf-data].MaxMemory\",\"mergedName\":\"MaxMemory\",\"fromId\":\"vm-with-perf\"},{\"originalName\":\"[sc-vms-arg-data].resourceGroup\"},{\"originalName\":\"[sc-vms-arg-data].subscriptionId\"},{\"originalName\":\"[sc-vms-perf-data].vmName\"}]}", "size": 0, "aggregation": 3, "noDataMessage": "No VMs found on this cluster.", "queryType": 7, "visualization": "table", "gridSettings": { "formatters": [ { "columnMatch": "vmName", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkLabel": "", "linkIsContextBlade": false, "linkColumn": "vmLink" } }, { "columnMatch": "vmLink", "formatter": 5 }, { "columnMatch": "status", "formatter": 18, "formatOptions": { "thresholdsOptions": "icons", "thresholdsGrid": [ { "operator": "==", "thresholdValue": "Connected", "representation": "success", "text": "{0}{1}" }, { "operator": "Default", "representation": "critical", "text": "{0}{1}" } ] } }, { "columnMatch": "AvgCPU", "formatter": 8, "formatOptions": { "min": 0, "max": 100, "palette": "greenRed" }, "numberFormat": { "unit": 1, "options": { "style": "decimal", "minimumFractionDigits": 1, "maximumFractionDigits": 1 } } }, { "columnMatch": "MaxCPU", "formatter": 0, "numberFormat": { "unit": 1, "options": { "style": "decimal", "minimumFractionDigits": 1, "maximumFractionDigits": 1 } } }, { "columnMatch": "AvgMemory", "formatter": 8, "formatOptions": { "min": 0, "max": 100, "palette": "greenRed" }, "numberFormat": { "unit": 1, "options": { "style": "decimal", "minimumFractionDigits": 1, "maximumFractionDigits": 1 } } }, { "columnMatch": "MaxMemory", "formatter": 0, "numberFormat": { "unit": 1, "options": { "style": "decimal", "minimumFractionDigits": 1, "maximumFractionDigits": 1 } } }, { "columnMatch": "resourceGroup", "formatter": 5 }, { "columnMatch": "subscriptionId", "formatter": 5 } ], "sortBy": [ { "itemKey": "clusterName", "sortOrder": 1 } ], "labelSettings": [ { "columnId": "vmName", "label": "VM Name" }, { "columnId": "clusterName", "label": "Cluster" }, { "columnId": "status", "label": "Status" }, { "columnId": "osSku", "label": "OS" }, { "columnId": "osVersion", "label": "OS Version" }, { "columnId": "AvgCPU", "label": "Avg CPU %" }, { "columnId": "MaxCPU", "label": "Peak CPU %" }, { "columnId": "AvgMemory", "label": "Avg Memory %" }, { "columnId": "MaxMemory", "label": "Peak Memory %" } ] }, "sortBy": [ { "itemKey": "clusterName", "sortOrder": 1 } ], "chartSettings": { "showLegend": false, "seriesLabelSettings": [] } }, "conditionalVisibility": { "parameterName": "ClusterFilter", "comparison": "isNotEqualTo", "value": "value::all" }, "name": "sc-vms-table" }, { "type": 1, "content": { "json": "### ☸️ AKS Arc Clusters on: {SingleCluster:label}" }, "name": "sc-aks-header" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "// AKS drilldown - no let statements (ARG constraint)\r\nresources\r\n| where type == \"microsoft.kubernetes/connectedclusters\"\r\n| extend aksIdLower = tolower(id)\r\n| join kind=inner (\r\n extensibilityresources\r\n | where type == \"microsoft.hybridcontainerservice/provisionedclusterinstances\"\r\n | extend customLocKey = tolower(trim_end(\"/\", tostring(extendedLocation.name)))\r\n | extend aksIdLower = tolower(substring(id, 0, indexof(id, \"/providers/Microsoft.HybridContainerService\")))\r\n | project aksIdLower, customLocKey\r\n) on aksIdLower\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.extendedlocation/customlocations\"\r\n | where tostring(properties.hostResourceId) contains \"Microsoft.ResourceConnector/appliances\"\r\n | extend arcBridgeRG = tolower(tostring(split(tostring(properties.hostResourceId), '/')[4]))\r\n | project customLocKey = tolower(trim_end(\"/\", id)), arcBridgeRG\r\n) on customLocKey\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where name =~ tostring(split('{SingleCluster}', '/')[8])\r\n | where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n | project hciClusterRG = tolower(resourceGroup)\r\n) on $left.arcBridgeRG == $right.hciClusterRG\r\n| extend aksName = name\r\n| extend connectivity = tostring(properties.connectivityStatus)\r\n| extend kubernetesVersion = tostring(properties.kubernetesVersion)\r\n| extend agentVersion = tostring(properties.agentVersion)\r\n| extend provisioningState = tostring(properties.provisioningState)\r\n| extend totalNodeCount = toint(properties.totalNodeCount)\r\n| extend aksLink = strcat('https://portal.azure.com/#@/resource', id)\r\n| project aksName, aksLink, clusterName = tostring(split('{SingleCluster}', '/')[8]), connectivity, kubernetesVersion, agentVersion, provisioningState, totalNodeCount, resourceGroup\r\n| order by clusterName asc, aksName asc", "size": 0, "aggregation": 3, "noDataMessage": "No AKS Arc clusters found on this cluster.", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "table", "gridSettings": { "formatters": [ { "columnMatch": "aksName", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkColumn": "aksLink", "linkLabel": "", "linkIsContextBlade": false } }, { "columnMatch": "aksLink", "formatter": 5 }, { "columnMatch": "connectivity", "formatter": 18, "formatOptions": { "thresholdsOptions": "icons", "thresholdsGrid": [ { "operator": "==", "thresholdValue": "Connected", "representation": "success", "text": "{0}{1}" }, { "operator": "Default", "representation": "critical", "text": "{0}{1}" } ] } } ], "labelSettings": [ { "columnId": "aksName", "label": "AKS Cluster" }, { "columnId": "clusterName", "label": "Azure Local Cluster" }, { "columnId": "connectivity", "label": "Status" }, { "columnId": "kubernetesVersion", "label": "K8s Version" }, { "columnId": "agentVersion", "label": "Agent Version" }, { "columnId": "provisioningState", "label": "Provisioning" }, { "columnId": "totalNodeCount", "label": "Node Count" }, { "columnId": "resourceGroup", "label": "Resource Group" } ] }, "chartSettings": { "showLegend": false, "seriesLabelSettings": [] } }, "conditionalVisibility": { "parameterName": "ClusterFilter", "comparison": "isNotEqualTo", "value": "value::all" }, "name": "sc-aks-table" }, { "type": 1, "content": { "json": "### πŸ“Š AKS Arc Node Resource Usage\nShowing current CPU and memory usage per AKS node from Azure Managed Prometheus. Requires [Azure Managed Prometheus](https://learn.microsoft.com/en-us/azure/azure-monitor/metrics/prometheus-metrics-overview) to be enabled on your AKS Arc clusters." }, "conditionalVisibility": { "parameterName": "ClusterFilter", "comparison": "isNotEqualTo", "value": "value::all" }, "name": "sc-aks-perf-header" }, { "type": 1, "content": { "json": "πŸ’‘ **Tip:** Select an **Azure Monitor Workspace** below to view AKS node resource usage. This requires [Azure Managed Prometheus](https://learn.microsoft.com/en-us/azure/azure-monitor/metrics/prometheus-metrics-overview) to be enabled on your AKS Arc clusters.\n\n⚠️ **Note:** These charts show data from **all AKS Arc clusters** reporting to the selected Azure Monitor Workspace, not just those on the selected Azure Local cluster. Prometheus metrics do not contain Azure Local cluster metadata, so cross-cluster filtering is not possible.", "style": "upsell" }, "conditionalVisibility": { "parameterName": "ClusterFilter", "comparison": "isNotEqualTo", "value": "value::all" }, "name": "sc-aks-amw-tip" }, { "type": 9, "content": { "version": "KqlParameterItem/1.0", "parameters": [ { "id": "azure-monitor-workspace-sc", "version": "KqlParameterItem/1.0", "name": "AzureMonitorWorkspace", "label": "Azure Monitor Workspace", "type": 2, "description": "Select the Azure Monitor Workspace collecting Prometheus metrics from your AKS Arc clusters.", "isRequired": true, "query": "resources\r\n| where type == \"microsoft.monitor/accounts\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| project value = id, label = name\r\n| order by label asc", "crossComponentResources": [ "{Subscriptions}" ], "typeSettings": { "additionalResourceOptions": [], "showDefault": false }, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources" } ], "style": "pills", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources" }, "conditionalVisibility": { "parameterName": "ClusterFilter", "comparison": "isNotEqualTo", "value": "value::all" }, "name": "sc-aks-amw-param" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "{\"version\":\"PrometheusQueryProvider/1.0\",\"customEndpoint\":false,\"queryText\":\"topk(10, avg by (cluster, instance) (1 - rate(node_cpu_seconds_total{mode=\\\"idle\\\"}[5m])) * 100)\",\"type\":\"query_range\"}", "size": 0, "aggregation": 3, "title": "Top 10 AKS Nodes β€” CPU Usage (Prometheus)", "noDataMessage": "No Prometheus data found. Ensure Azure Managed Prometheus is enabled and the selected Azure Monitor Workspace receives metrics.", "timeContext": { "durationMs": 3600000 }, "queryType": 16, "resourceType": "microsoft.monitor/accounts", "crossComponentResources": [ "{AzureMonitorWorkspace}" ], "visualization": "timechart", "chartSettings": { "group": "*", "createOtherGroup": 50, "ySettings": { "min": 0, "max": 100, "numberFormatSettings": { "unit": 1, "options": { "style": "decimal", "useGrouping": true, "minimumFractionDigits": 0, "maximumFractionDigits": 1 } } }, "showLegend": true } }, "customWidth": "50", "conditionalVisibility": { "parameterName": "ClusterFilter", "comparison": "isNotEqualTo", "value": "value::all" }, "name": "sc-aks-cpu-table" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "{\"version\":\"PrometheusQueryProvider/1.0\",\"customEndpoint\":false,\"queryText\":\"topk(10, (1 - avg by (cluster, instance) (node_memory_MemAvailable_bytes) / avg by (cluster, instance) (node_memory_MemTotal_bytes)) * 100)\",\"type\":\"query_range\"}", "size": 0, "aggregation": 3, "title": "Top 10 AKS Nodes β€” Memory Usage (Prometheus)", "noDataMessage": "No Prometheus data found. Ensure Azure Managed Prometheus is enabled and the selected Azure Monitor Workspace receives metrics.", "timeContext": { "durationMs": 3600000 }, "queryType": 16, "resourceType": "microsoft.monitor/accounts", "crossComponentResources": [ "{AzureMonitorWorkspace}" ], "visualization": "timechart", "chartSettings": { "group": "*", "createOtherGroup": 50, "ySettings": { "min": 0, "max": 100, "numberFormatSettings": { "unit": 1, "options": { "style": "decimal", "useGrouping": true, "minimumFractionDigits": 0, "maximumFractionDigits": 1 } } }, "showLegend": true } }, "customWidth": "50", "conditionalVisibility": { "parameterName": "ClusterFilter", "comparison": "isNotEqualTo", "value": "value::all" }, "name": "sc-aks-memory-table" } ] }, "conditionalVisibility": { "parameterName": "CapacitySection", "comparison": "isEqualTo", "value": "single" }, "name": "cap-single-section" } ] }, "conditionalVisibility": { "parameterName": "selectedTab", "comparison": "isEqualTo", "value": "7" }, "name": "capacity-tab-group" }, { "type": 12, "content": { "version": "NotebookGroup/1.0", "groupType": "editable", "items": [ { "type": 12, "content": { "version": "NotebookGroup/1.0", "groupType": "editable", "items": [ { "type": 1, "content": { "json": "## πŸ”— ARB Status per Azure Local instance" }, "name": "text - 2" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.resourceconnector/appliances\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| extend status = tostring(properties.status)\r\n| project arcbridgename = name, resourceGroup, status\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | extend hcistatus = tostring(properties.status)\r\n | project hciname = name, resourceGroup, hcistatus\r\n) on resourceGroup\r\n| extend hcistatus = coalesce(hcistatus, \"Unknown\")\r\n| summarize \r\n TotalResources = count(),\r\n ArcBridgeCount = dcount(arcbridgename),\r\n HCIClusterCount = dcount(hciname)\r\n by ArcBridgeStatus = status, HCIClusterConnectivity = hcistatus\r\n| extend sortOrder = case(ArcBridgeStatus == \"Running\", 0, ArcBridgeStatus == \"Offline\", 2, 1)\r\n| order by sortOrder asc\r\n| project-away sortOrder", "size": 0, "showRefreshButton": true, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "gridSettings": { "rowLimit": 2000, "formatters": [ { "columnMatch": "TotalResources", "formatter": 5 }, { "columnMatch": "HCIClusterCount", "formatter": 5 }, { "columnMatch": "ArcBridgeStatus", "formatter": 18, "formatOptions": { "thresholdsOptions": "icons", "thresholdsGrid": [ { "operator": "==", "thresholdValue": "Running", "representation": "success", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "Offline", "representation": "4", "text": "{0}{1}" }, { "operator": "Default", "thresholdValue": null, "representation": "warning", "text": "{0}{1}" } ] } } ] }, "sortBy": [] }, "customWidth": "50", "name": "arcbridge-status" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "{\"version\":\"Merge/1.0\",\"merges\":[{\"id\":\"b39a061e-3234-4dbf-aeb0-c96077ed8030\",\"mergeType\":\"table\",\"leftTable\":\"arcbridge-status\"}],\"projectRename\":[{\"originalName\":\"[arcbridge-status].ArcBridgeStatus\",\"mergedName\":\"ArcBridgeStatus\",\"fromId\":\"b39a061e-3234-4dbf-aeb0-c96077ed8030\"},{\"originalName\":\"[arcbridge-status].HCIStatus\",\"mergedName\":\"HCIStatus\",\"fromId\":\"b39a061e-3234-4dbf-aeb0-c96077ed8030\"},{\"originalName\":\"[arcbridge-status].TotalResources\",\"mergedName\":\"TotalResources\",\"fromId\":\"b39a061e-3234-4dbf-aeb0-c96077ed8030\"},{\"originalName\":\"[arcbridge-status].ArcBridgeCount\",\"mergedName\":\"ArcBridgeCount\",\"fromId\":\"b39a061e-3234-4dbf-aeb0-c96077ed8030\"},{\"originalName\":\"[arcbridge-status].HCIClusterCount\",\"mergedName\":\"HCIClusterCount\",\"fromId\":\"b39a061e-3234-4dbf-aeb0-c96077ed8030\"},{\"originalName\":\"[arcbridge-status].ClusterConnectivityStatus\",\"mergedName\":\"ClusterConnectivityStatus\",\"fromId\":\"unknown\"},{\"originalName\":\"[arcbridge-status].ClusterConnectivity\",\"mergedName\":\"ClusterConnectivity\",\"fromId\":\"unknown\"}]}", "size": 2, "queryType": 7, "visualization": "piechart" }, "customWidth": "50", "name": "query - 1" } ] }, "customWidth": "100", "conditionalVisibility": { "parameterName": "selectedTab", "comparison": "isEqualTo", "value": "1" }, "name": "arcbridge summary", "styleSettings": { "showBorder": true } }, { "type": 1, "content": { "json": "## ⚠️ Offline Azure Resource Bridges (ARB) appliances" }, "name": "text - 2 - Copy" }, { "type": 1, "content": { "json": "⚠️ **Warning:** Azure resource bridge appliance can't be offline for more than 45 days because this inactivity can affect the validity of the security key used for authentication. [Learn more](https://learn.microsoft.com/azure/azure-arc/resource-bridge/troubleshoot-resource-bridge#arc-resource-bridge-is-offline)" }, "name": "arb-offline-warning" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where (type == \"microsoft.hybridcompute/machines\" and kind == \"HCI\")\r\n or (type == \"microsoft.kubernetes/connectedclusters\" and tostring(properties.infrastructure) == \"azure_stack_hci\")\r\n| extend isVM = (type == \"microsoft.hybridcompute/machines\")\r\n| extend resourceId = tolower(id)\r\n| join kind=inner (\r\n extensibilityresources\r\n | where type in (\"microsoft.azurestackhci/virtualmachineinstances\", \"microsoft.hybridcontainerservice/provisionedclusterinstances\")\r\n | extend customLocId = tolower(trim_end(\"/\", tostring(extendedLocation.name)))\r\n | extend parentId = tolower(iff(\r\n type == \"microsoft.azurestackhci/virtualmachineinstances\",\r\n substring(id, 0, indexof(id, \"/providers/Microsoft.AzureStackHCI\")),\r\n substring(id, 0, indexof(id, \"/providers/Microsoft.HybridContainerService\"))))\r\n | project parentId, customLocId\r\n) on $left.resourceId == $right.parentId\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.extendedlocation/customlocations\"\r\n | extend arcBridgeRG = tolower(tostring(split(tostring(properties.hostResourceId), '/')[4]))\r\n | project customLocId = tolower(trim_end(\"/\", id)), arcBridgeRG\r\n) on customLocId\r\n| summarize VMCount = countif(isVM), AKSArcCount = countif(not(isVM)) by arcBridgeRG", "size": 0, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ] }, "conditionalVisibility": { "parameterName": "neverVisible", "comparison": "isEqualTo", "value": "alwaysHidden" }, "name": "arb-vm-aks-counts" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.resourceconnector/appliances\"\r\n| where properties.status == \"Offline\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| extend status = tostring(properties.status)\r\n| extend lastModified = todatetime(systemData.lastModifiedAt)\r\n| extend daysSinceLastModified = datetime_diff('day', now(), lastModified)\r\n| project arcbridgename = name, arcbridgeId = id, resourceGroup, status, lastModified, daysSinceLastModified\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | extend hcistatus = tostring(properties.status)\r\n | project hciname = name, clusterId = id, resourceGroup, hcistatus\r\n | join kind=leftouter (\r\n ExtensibilityResources\r\n | where type =~ 'microsoft.azurestackhci/clusters/updateSummaries'\r\n | extend currentVersion = tostring(properties.currentVersion)\r\n | extend cId = substring(id, 0, indexof(id, '/updateSummaries/'))\r\n | project cId, currentVersion\r\n ) on $left.clusterId == $right.cId\r\n\r\n) on resourceGroup\r\n| extend arbLink = strcat('https://portal.azure.com/#@/resource', arcbridgeId)\r\n| extend clusterLink = strcat('https://portal.azure.com/#@/resource', clusterId)\r\n| project ARBResourceName = arcbridgename, arbLink, resourceGroup, ARBStatus = status, lastModified, daysSinceLastModified, ClusterName = coalesce(hciname, 'Unknown'), clusterLink, HCIClusterConnectivity = coalesce(hcistatus, 'Unknown'), currentVersion\r\n| order by daysSinceLastModified desc, ARBStatus asc", "size": 0, "showRefreshButton": true, "showExportToExcel": true, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ] }, "name": "arb-offline-base", "conditionalVisibility": { "parameterName": "neverVisible", "comparison": "isEqualTo", "value": "alwaysHidden" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "{\"version\": \"Merge/1.0\", \"merges\": [{\"id\": \"arb-merge\", \"mergeType\": \"leftouter\", \"leftTable\": \"arb-offline-base\", \"rightTable\": \"arb-vm-aks-counts\", \"leftColumn\": \"resourceGroup\", \"rightColumn\": \"arcBridgeRG\"}], \"projectRename\": [{\"originalName\": \"[arb-offline-base].ARBResourceName\", \"mergedName\": \"ARBResourceName\", \"fromId\": \"arb-merge\"}, {\"originalName\": \"[arb-offline-base].arbLink\", \"mergedName\": \"arbLink\", \"fromId\": \"arb-merge\"}, {\"originalName\": \"[arb-offline-base].resourceGroup\", \"mergedName\": \"resourceGroup\", \"fromId\": \"arb-merge\"}, {\"originalName\": \"[arb-offline-base].ARBStatus\", \"mergedName\": \"ARBStatus\", \"fromId\": \"arb-merge\"}, {\"originalName\": \"[arb-offline-base].lastModified\", \"mergedName\": \"lastModified\", \"fromId\": \"arb-merge\"}, {\"originalName\": \"[arb-offline-base].daysSinceLastModified\", \"mergedName\": \"daysSinceLastModified\", \"fromId\": \"arb-merge\"}, {\"originalName\": \"[arb-offline-base].ClusterName\", \"mergedName\": \"ClusterName\", \"fromId\": \"arb-merge\"}, {\"originalName\": \"[arb-offline-base].clusterLink\", \"mergedName\": \"clusterLink\", \"fromId\": \"arb-merge\"}, {\"originalName\": \"[arb-offline-base].HCIClusterConnectivity\", \"mergedName\": \"HCIClusterConnectivity\", \"fromId\": \"arb-merge\"}, {\"originalName\": \"[arb-offline-base].currentVersion\", \"mergedName\": \"currentVersion\", \"fromId\": \"arb-merge\"}, {\"originalName\": \"[arb-vm-aks-counts].VMCount\", \"mergedName\": \"VMCount\", \"fromId\": \"arb-merge\"}, {\"originalName\": \"[arb-vm-aks-counts].AKSArcCount\", \"mergedName\": \"AKSArcCount\", \"fromId\": \"arb-merge\"}, {\"originalName\": \"[arb-offline-base].resourceGroup\"}, {\"originalName\": \"[arb-vm-aks-counts].arcBridgeRG\"}]}", "size": 0, "showRefreshButton": true, "showExportToExcel": true, "queryType": 7, "visualization": "table", "gridSettings": { "rowLimit": 2000, "formatters": [ { "columnMatch": "ARBResourceName", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkColumn": "arbLink" } }, { "columnMatch": "arbLink", "formatter": 5 }, { "columnMatch": "ARBStatus", "formatter": 18, "formatOptions": { "thresholdsOptions": "icons", "thresholdsGrid": [ { "operator": "==", "thresholdValue": "Running", "representation": "success", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "Offline", "representation": "4", "text": "{0}{1}" }, { "operator": "Default", "thresholdValue": null, "representation": "warning", "text": "{0}{1}" } ] } }, { "columnMatch": "lastModified", "formatter": 6 }, { "columnMatch": "daysSinceLastModified", "formatter": 18, "formatOptions": { "thresholdsOptions": "colors", "thresholdsGrid": [ { "operator": ">", "thresholdValue": "14", "representation": "redBright", "text": "{0}{1} days" }, { "operator": ">=", "thresholdValue": "1", "representation": "yellow", "text": "{0}{1} days" }, { "operator": "Default", "thresholdValue": null, "representation": "green", "text": "{0}{1} days" } ] } }, { "columnMatch": "ClusterName", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkColumn": "clusterLink" } }, { "columnMatch": "clusterLink", "formatter": 5 }, { "columnMatch": "HCIClusterConnectivity", "formatter": 18, "formatOptions": { "thresholdsOptions": "icons", "thresholdsGrid": [ { "operator": "==", "thresholdValue": "ConnectedRecently", "representation": "success", "text": "{0}{1}" }, { "operator": "Default", "thresholdValue": null, "representation": "4", "text": "{0}{1}" } ] } } ], "filter": true, "labelSettings": [ { "columnId": "ARBResourceName", "label": "ARB Resource Name" }, { "columnId": "arbLink", "label": "ARB Link" }, { "columnId": "resourceGroup", "label": "Resource Group" }, { "columnId": "ARBStatus", "label": "ARB Status" }, { "columnId": "lastModified", "label": "Last Modified" }, { "columnId": "daysSinceLastModified", "label": "Days Since Last Modified" }, { "columnId": "ClusterName", "label": "Cluster Name" }, { "columnId": "clusterLink" }, { "columnId": "HCIClusterConnectivity", "label": "HCI Cluster Connectivity" }, { "columnId": "currentVersion", "label": "Current Version" }, { "columnId": "VMCount", "label": "VM Count" }, { "columnId": "AKSArcCount", "label": "AKS Arc Count" } ] }, "sortBy": [] }, "name": "arb-offline-table" }, { "type": 11, "content": { "version": "LinkItem/1.0", "style": "nav", "links": [ { "id": "arb-offline-troubleshoot-link", "cellValue": "https://learn.microsoft.com/azure/azure-arc/resource-bridge/troubleshoot-resource-bridge#arc-resource-bridge-is-offline", "linkTarget": "Url", "linkLabel": "πŸ“š Knowledge Link: Troubleshoot Azure Resource Bridge (ARB) Offline status", "style": "link" } ] }, "name": "arb-offline-knowledge-link" }, { "type": 1, "content": { "json": "## πŸ”— All Azure Resource Bridges (ARB) appliances" }, "name": "text - 3" }, { "type": 9, "content": { "version": "KqlParameterItem/1.0", "parameters": [ { "id": "arb-status-filter", "version": "KqlParameterItem/1.0", "name": "ArbStatusFilter", "label": "ARB Status", "type": 2, "isRequired": true, "multiSelect": true, "quote": "'", "delimiter": ",", "query": "resources\r\n| where type == \"microsoft.resourceconnector/appliances\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| extend status = tostring(properties.status)\r\n| distinct status\r\n| order by status asc", "crossComponentResources": [ "{Subscriptions}" ], "typeSettings": { "additionalResourceOptions": [ "value::all" ], "showDefault": false }, "defaultValue": "value::all", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources" }, { "id": "arb-cluster-filter", "version": "KqlParameterItem/1.0", "name": "ArbClusterFilter", "label": "Cluster Name", "type": 2, "isRequired": true, "multiSelect": true, "quote": "'", "delimiter": ",", "query": "resources\r\n| where type == \"microsoft.azurestackhci/clusters\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n| distinct name\r\n| order by name asc", "crossComponentResources": [ "{Subscriptions}" ], "typeSettings": { "additionalResourceOptions": [ "value::all" ], "showDefault": false }, "defaultValue": "value::all", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources" } ], "style": "pills", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources" }, "name": "arb-all-filters" }, { "type": 1, "content": { "json": "πŸ’‘ **Tip:** If the 'Cluster Name' column shows 'Unknown', the ARB may have been orphaned. Check if the cluster resource has been deleted." }, "name": "arb-orphaned-tip" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.resourceconnector/appliances\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| extend status = tostring(properties.status)\r\n| extend lastModified = todatetime(systemData.lastModifiedAt)\r\n| extend daysSinceLastModified = iff(status == \"Running\", toint(-1), datetime_diff('day', now(), lastModified))\r\n| project arcbridgename = name, arcbridgeId = id, resourceGroup, status, lastModified, daysSinceLastModified\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | extend hcistatus = tostring(properties.status)\r\n | project hciname = name, clusterId = id, resourceGroup, hcistatus\r\n | join kind=leftouter (\r\n ExtensibilityResources\r\n | where type =~ 'microsoft.azurestackhci/clusters/updateSummaries'\r\n | extend currentVersion = tostring(properties.currentVersion)\r\n | extend cId = substring(id, 0, indexof(id, '/updateSummaries/'))\r\n | project cId, currentVersion\r\n ) on $left.clusterId == $right.cId\r\n\r\n) on resourceGroup\r\n| where '*' in ({ArbStatusFilter}) or status in ({ArbStatusFilter})\r\n| where '*' in ({ArbClusterFilter}) or hciname in ({ArbClusterFilter}) or isempty(hciname)\r\n| extend arbLink = strcat('https://portal.azure.com/#@/resource', arcbridgeId)\r\n| extend clusterLink = strcat('https://portal.azure.com/#@/resource', clusterId)\r\n| project ARBResourceName = arcbridgename, arbLink, resourceGroup, ARBStatus = status, lastModified, daysSinceLastModified, ClusterName = coalesce(hciname, 'Unknown'), clusterLink, HCIClusterConnectivity = coalesce(hcistatus, 'Unknown'), currentVersion\r\n| order by HCIClusterConnectivity desc, ARBStatus asc", "size": 0, "showRefreshButton": true, "showExportToExcel": true, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ] }, "name": "arb-all-base", "conditionalVisibility": { "parameterName": "neverVisible", "comparison": "isEqualTo", "value": "alwaysHidden" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "{\"version\": \"Merge/1.0\", \"merges\": [{\"id\": \"arb-merge\", \"mergeType\": \"leftouter\", \"leftTable\": \"arb-all-base\", \"rightTable\": \"arb-vm-aks-counts\", \"leftColumn\": \"resourceGroup\", \"rightColumn\": \"arcBridgeRG\"}], \"projectRename\": [{\"originalName\": \"[arb-all-base].ARBResourceName\", \"mergedName\": \"ARBResourceName\", \"fromId\": \"arb-merge\"}, {\"originalName\": \"[arb-all-base].arbLink\", \"mergedName\": \"arbLink\", \"fromId\": \"arb-merge\"}, {\"originalName\": \"[arb-all-base].resourceGroup\", \"mergedName\": \"resourceGroup\", \"fromId\": \"arb-merge\"}, {\"originalName\": \"[arb-all-base].ARBStatus\", \"mergedName\": \"ARBStatus\", \"fromId\": \"arb-merge\"}, {\"originalName\": \"[arb-all-base].lastModified\", \"mergedName\": \"lastModified\", \"fromId\": \"arb-merge\"}, {\"originalName\": \"[arb-all-base].daysSinceLastModified\", \"mergedName\": \"daysSinceLastModified\", \"fromId\": \"arb-merge\"}, {\"originalName\": \"[arb-all-base].ClusterName\", \"mergedName\": \"ClusterName\", \"fromId\": \"arb-merge\"}, {\"originalName\": \"[arb-all-base].clusterLink\", \"mergedName\": \"clusterLink\", \"fromId\": \"arb-merge\"}, {\"originalName\": \"[arb-all-base].HCIClusterConnectivity\", \"mergedName\": \"HCIClusterConnectivity\", \"fromId\": \"arb-merge\"}, {\"originalName\": \"[arb-all-base].currentVersion\", \"mergedName\": \"currentVersion\", \"fromId\": \"arb-merge\"}, {\"originalName\": \"[arb-vm-aks-counts].VMCount\", \"mergedName\": \"VMCount\", \"fromId\": \"arb-merge\"}, {\"originalName\": \"[arb-vm-aks-counts].AKSArcCount\", \"mergedName\": \"AKSArcCount\", \"fromId\": \"arb-merge\"}, {\"originalName\": \"[arb-all-base].resourceGroup\"}, {\"originalName\": \"[arb-vm-aks-counts].arcBridgeRG\"}]}", "size": 0, "showRefreshButton": true, "showExportToExcel": true, "queryType": 7, "visualization": "table", "gridSettings": { "rowLimit": 2000, "formatters": [ { "columnMatch": "arbLink", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkLabel": "Open ARB" } }, { "columnMatch": "ARBStatus", "formatter": 18, "formatOptions": { "thresholdsOptions": "icons", "thresholdsGrid": [ { "operator": "==", "thresholdValue": "Running", "representation": "success", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "Offline", "representation": "4", "text": "{0}{1}" }, { "operator": "Default", "thresholdValue": null, "representation": "warning", "text": "{0}{1}" } ] } }, { "columnMatch": "lastModified", "formatter": 6 }, { "columnMatch": "daysSinceLastModified", "formatter": 18, "formatOptions": { "thresholdsOptions": "colors", "thresholdsGrid": [ { "operator": "<", "thresholdValue": "0", "representation": "green", "text": "Connected" }, { "operator": ">", "thresholdValue": "45", "representation": "redBright", "text": "{0}{1} days" }, { "operator": "Default", "thresholdValue": null, "representation": "yellow", "text": "{0}{1} days" } ] } }, { "columnMatch": "ClusterName", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkColumn": "clusterLink" } }, { "columnMatch": "clusterLink", "formatter": 5 }, { "columnMatch": "HCIClusterConnectivity", "formatter": 18, "formatOptions": { "thresholdsOptions": "icons", "thresholdsGrid": [ { "operator": "==", "thresholdValue": "ConnectedRecently", "representation": "success", "text": "{0}{1}" }, { "operator": "Default", "thresholdValue": null, "representation": "4", "text": "{0}{1}" } ] } } ], "filter": true, "labelSettings": [ { "columnId": "ARBResourceName", "label": "ARB Resource Name" }, { "columnId": "arbLink", "label": "ARB Link" }, { "columnId": "resourceGroup", "label": "Resource Group" }, { "columnId": "ARBStatus", "label": "ARB Status" }, { "columnId": "lastModified", "label": "Last Modified" }, { "columnId": "daysSinceLastModified", "label": "Days Since Last Modified" }, { "columnId": "ClusterName", "label": "Cluster Name" }, { "columnId": "clusterLink" }, { "columnId": "HCIClusterConnectivity", "label": "HCI Cluster Connectivity" }, { "columnId": "currentVersion", "label": "Current Version" }, { "columnId": "VMCount", "label": "VM Count" }, { "columnId": "AKSArcCount", "label": "AKS Arc Count" } ] }, "sortBy": [] }, "name": "arb-all-table" }, { "type": 1, "content": { "json": "---\r\n## πŸ”” ARB Alert Rules Configuration\r\nπŸ’‘ **Tip:** Create alert rules to monitor Arc Resource Bridge(ARB) health and status. Get notified when ARBs go offline or encounter issues. Expand below section to view a step by step guide." }, "name": "arb-alert-header" }, { "type": 9, "content": { "version": "KqlParameterItem/1.0", "parameters": [ { "id": "arb-alert-toggle", "version": "KqlParameterItem/1.0", "name": "ShowAlertRulesSection", "label": "Show Alert Rules Setup", "type": 10, "isRequired": true, "typeSettings": { "additionalResourceOptions": [], "showDefault": false }, "jsonData": "[{\"value\": \"yes\", \"label\": \"Yes - Show Alert Setup\"}, {\"value\": \"no\", \"label\": \"No - Hide Alert Setup\"}]", "value": "no" } ], "style": "pills", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces" }, "name": "arb-alert-toggle-param" }, { "type": 12, "content": { "version": "NotebookGroup/1.0", "groupType": "editable", "items": [ { "type": 1, "content": { "json": "### πŸ“‹ Prerequisites for Alert Rules\r\n\r\n1. **Action Group**: Create or use an existing [Action Group](https://portal.azure.com/#view/Microsoft_Azure_Monitoring/ActionGroupsTemplateBlade) to receive notifications (Email, SMS, Webhook, etc.)\r\n2. **Permissions**: Contributor or Monitoring Contributor role on the subscription/resource group\r\n3. **Resource Health**: Azure Resource Health alerts are supported for Arc Resource Bridge\r\n\r\n---" }, "name": "arb-alert-prereqs" }, { "type": 1, "content": { "json": "### 🎯 Recommended Alert Rules for Arc Resource Bridge\r\n\r\n| Alert Type | Description | Recommended Severity |\r\n|------------|-------------|---------------------|\r\n| **Offline Status** | ARB appliance status is 'Offline' | Sev 1 (Critical) |\r\n| **Resource Health - Unavailable** | ARB resource health state is unavailable | Sev 1 (Critical) |\r\n| **Resource Health - Degraded** | ARB resource health state is degraded | Sev 2 (Warning) |\r\n| **Extended Offline (>14 days)** | ARB has been offline for more than 14 days | Sev 0 (Critical) |\r\n\r\n⚠️ **Important**: Arc Resource Bridge can't be offline for more than 45 days or the security key will expire.\r\n\r\n---" }, "name": "arb-alert-types" }, { "type": 1, "content": { "json": "### πŸš€ Create Alert Rules\r\n\r\nSelect the Arc Resource Bridges below to create alert rules. Click the **Create Alert Rule** link to open Azure Portal with pre-configured settings." }, "name": "arb-alert-instructions" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.resourceconnector/appliances\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| extend status = tostring(properties.status)\r\n| extend lastModified = todatetime(systemData.lastModifiedAt)\r\n| extend daysSinceLastModifiedNum = datetime_diff('day', now(), lastModified)\r\n| extend ResourceHealthAlertLink = strcat('https://portal.azure.com/#view/Microsoft_Azure_Monitoring/CreateAlertRuleFromResourceBlade/resourceId/', url_encode(id), '/alertType/ResourceHealth')\r\n| extend ActivityLogAlertLink = strcat('https://portal.azure.com/#view/Microsoft_Azure_Monitoring/CreateAlertRuleFromResourceBlade/resourceId/', url_encode(id), '/alertType/ActivityLog')\r\n| project \r\n ARBName = name, \r\n ResourceId = id,\r\n ResourceGroup = resourceGroup,\r\n SubscriptionId = subscriptionId,\r\n Status = status,\r\n DaysOffline = iff(status == \"Running\", 0, daysSinceLastModifiedNum),\r\n ResourceHealthAlertLink,\r\n ActivityLogAlertLink\r\n| join kind=leftouter (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project ClusterName = name, resourceGroup\r\n) on $left.ResourceGroup == $right.resourceGroup\r\n| extend ClusterName = coalesce(ClusterName, 'N/A')\r\n| project ARBName, ResourceGroup, ClusterName, Status, DaysOffline, ResourceHealthAlertLink, ActivityLogAlertLink, ResourceId\r\n| order by Status desc, DaysOffline desc", "size": 0, "showRefreshButton": true, "showExportToExcel": true, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "gridSettings": { "rowLimit": 2000, "formatters": [ { "columnMatch": "Status", "formatter": 18, "formatOptions": { "thresholdsOptions": "icons", "thresholdsGrid": [ { "operator": "==", "thresholdValue": "Running", "representation": "success", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "Offline", "representation": "4", "text": "{0}{1}" }, { "operator": "Default", "thresholdValue": null, "representation": "warning", "text": "{0}{1}" } ] } }, { "columnMatch": "DaysOffline", "formatter": 18, "formatOptions": { "thresholdsOptions": "colors", "thresholdsGrid": [ { "operator": ">", "thresholdValue": "14", "representation": "redBright", "text": "{0}{1} days" }, { "operator": ">=", "thresholdValue": "1", "representation": "yellow", "text": "{0}{1} days" }, { "operator": "Default", "thresholdValue": null, "representation": "green", "text": "Online" } ] } }, { "columnMatch": "ResourceHealthAlertLink", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkLabel": "πŸ”” Resource Health Alert" } }, { "columnMatch": "ActivityLogAlertLink", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkLabel": "πŸ“‹ Activity Log Alert" } }, { "columnMatch": "ResourceId", "formatter": 5 } ], "filter": true, "labelSettings": [ { "columnId": "ARBName", "label": "Arc Resource Bridge" }, { "columnId": "ResourceGroup", "label": "Resource Group" }, { "columnId": "ClusterName", "label": "Associated Cluster" }, { "columnId": "DaysOffline", "label": "Days Offline" }, { "columnId": "ResourceHealthAlertLink", "label": "Resource Health Alert" }, { "columnId": "ActivityLogAlertLink", "label": "Activity Log Alert" } ] } }, "name": "arb-alert-rules-table" }, { "type": 1, "content": { "json": "---\r\n### πŸ“ Manual Alert Rule Creation Steps\r\n\r\n**Option 1: Resource Health Alert (Recommended for Offline Detection)**\r\n1. Click **Resource Health Alert** link above for the specific ARB\r\n2. Configure the alert condition:\r\n - **Current resource status**: Select `Unavailable`\r\n - **Previous resource status**: Select `Available`\r\n - **Reason type**: Select `Platform Initiated` and `User Initiated`\r\n3. Select or create an **Action Group**\r\n4. Set alert severity (Sev 1 recommended for offline)\r\n5. Name the alert rule and create\r\n\r\n**Option 2: Activity Log Alert (For status changes)**\r\n1. Click **Activity Log Alert** link above for the specific ARB\r\n2. Select signal: `All Administrative operations`\r\n3. Filter by operation: `Microsoft.ResourceConnector/appliances/write`\r\n4. Select or create an **Action Group**\r\n5. Name the alert rule and create\r\n\r\n**Option 3: Azure Resource Graph Alert (Advanced - For bulk monitoring)**\r\n1. Go to [Azure Monitor Alert Rules](https://portal.azure.com/#view/Microsoft_Azure_Monitoring/AzureMonitoringBrowseBlade/~/alertsV2)\r\n2. Create a new **Log Search Alert**\r\n3. Use the following ARG query:\r\n```kusto\r\narg(\"\").resources\r\n| where type == \"microsoft.resourceconnector/appliances\"\r\n| where properties.status == \"Offline\"\r\n| project name, resourceGroup, status = tostring(properties.status)\r\n```\r\n4. Set condition: `Number of results > 0`\r\n5. Configure frequency (e.g., every 15 minutes)\r\n6. Select Action Group and create" }, "name": "arb-manual-alert-steps" }, { "type": 11, "content": { "version": "LinkItem/1.0", "style": "nav", "links": [ { "id": "create-action-group-link", "cellValue": "https://portal.azure.com/#view/Microsoft_Azure_Monitoring/ActionGroupsTemplateBlade", "linkTarget": "Url", "linkLabel": "βž• Create Action Group", "style": "link" }, { "id": "view-alert-rules-link", "cellValue": "https://portal.azure.com/#view/Microsoft_Azure_Monitoring/AzureMonitoringBrowseBlade/~/alertsV2", "linkTarget": "Url", "linkLabel": "πŸ“‹ View Existing Alert Rules", "style": "link" }, { "id": "resource-health-docs-link", "cellValue": "https://learn.microsoft.com/azure/service-health/resource-health-alert-monitor-guide", "linkTarget": "Url", "linkLabel": "πŸ“š Resource Health Alerts Docs", "style": "link" }, { "id": "arb-troubleshoot-link", "cellValue": "https://learn.microsoft.com/azure/azure-arc/resource-bridge/troubleshoot-resource-bridge", "linkTarget": "Url", "linkLabel": "πŸ”§ Troubleshoot ARB", "style": "link" } ] }, "name": "arb-alert-quick-links" } ] }, "conditionalVisibility": { "parameterName": "ShowAlertRulesSection", "comparison": "isEqualTo", "value": "yes" }, "name": "arb-alert-rules-group" } ] }, "conditionalVisibility": { "parameterName": "selectedTab", "comparison": "isEqualTo", "value": "1" }, "name": "arcbridge-page" }, { "type": 12, "content": { "version": "NotebookGroup/1.0", "groupType": "editable", "title": "Azure Local Machines", "items": [ { "type": 1, "content": { "json": "## πŸ–₯️ Azure Local Physical Machines\r\nPhysical server machines that make up Azure Local (Azure Stack HCI) clusters, connected through Azure Arc.\r\n\r\n**Last Refreshed:** {TimeRange:label} | Click refresh on any tile to update data" }, "name": "text-nodes-header" }, { "type": 11, "content": { "version": "LinkItem/1.0", "style": "nav", "links": [ { "id": "arc-agent-docs-link", "cellValue": "https://learn.microsoft.com/azure/azure-arc/servers/troubleshoot-agent-onboard", "linkTarget": "Url", "linkLabel": "πŸ“š Documentation: Troubleshoot Azure Arc Agent", "style": "link" } ] }, "name": "arc-agent-docs-link" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.hybridcompute/machines\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where properties.cloudMetadata.provider == \"AzSHCI\"\r\n| where kind != \"HCI\"\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterResourceGroup = resourceGroup\r\n) on $left.resourceGroup == $right.clusterResourceGroup\r\n| summarize Total = count()\r\n| extend Label = 'Total Machines'", "size": 4, "title": "Total", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "tiles", "tileSettings": { "titleContent": { "columnMatch": "Label", "formatter": 1 }, "leftContent": { "columnMatch": "Total", "formatter": 12, "formatOptions": { "palette": "blue" } }, "showBorder": true, "size": "auto" } }, "customWidth": "15", "name": "tile-total-machines", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.hybridcompute/machines\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where properties.cloudMetadata.provider == \"AzSHCI\"\r\n| where kind != \"HCI\"\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterResourceGroup = resourceGroup\r\n) on $left.resourceGroup == $right.clusterResourceGroup\r\n| extend status = tostring(properties.status)\r\n| where status == \"Connected\"\r\n| summarize Connected = count()\r\n| extend Label = 'Connected'", "size": 4, "title": "Connected", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "tiles", "tileSettings": { "titleContent": { "columnMatch": "Label", "formatter": 18, "formatOptions": { "thresholdsOptions": "colors", "thresholdsGrid": [ { "operator": "Default", "thresholdValue": null, "representation": "green", "text": "{0}{1}" } ] } }, "leftContent": { "columnMatch": "Connected", "formatter": 12, "formatOptions": { "palette": "green" } }, "showBorder": true, "size": "auto" } }, "customWidth": "15", "name": "tile-connected-machines", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.hybridcompute/machines\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where properties.cloudMetadata.provider == \"AzSHCI\"\r\n| where kind != \"HCI\"\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterResourceGroup = resourceGroup\r\n) on $left.resourceGroup == $right.clusterResourceGroup\r\n| extend status = tostring(properties.status)\r\n| where status == \"Disconnected\"\r\n| summarize Disconnected = count()\r\n| extend Label = 'Disconnected'", "size": 4, "title": "Disconnected", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "tiles", "tileSettings": { "titleContent": { "columnMatch": "Label", "formatter": 18, "formatOptions": { "thresholdsOptions": "colors", "thresholdsGrid": [ { "operator": "Default", "thresholdValue": null, "representation": "redBright", "text": "{0}{1}" } ] } }, "leftContent": { "columnMatch": "Disconnected", "formatter": 12, "formatOptions": { "palette": "redBright" } }, "showBorder": true, "size": "auto" } }, "customWidth": "15", "name": "tile-disconnected-machines", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 1, "content": { "json": "## πŸ’» Machine Overview" }, "name": "text-machine-overview-header" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.hybridcompute/machines\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where properties.cloudMetadata.provider == \"AzSHCI\"\r\n| where kind != \"HCI\"\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterResourceGroup = resourceGroup\r\n) on $left.resourceGroup == $right.clusterResourceGroup\r\n| extend status = tostring(properties.status)\r\n| summarize Count = count() by status\r\n| order by Count desc", "size": 0, "title": "Connection Status", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "piechart", "chartSettings": { "showLegend": true, "legendPosition": "bottom", "seriesLabelSettings": [] } }, "customWidth": "33", "name": "node-connection-pie", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.hybridcompute/machines\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where properties.cloudMetadata.provider == \"AzSHCI\"\r\n| where kind != \"HCI\"\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterResourceGroup = resourceGroup\r\n) on $left.resourceGroup == $right.clusterResourceGroup\r\n| extend manufacturer = coalesce(tostring(properties.detectedProperties.manufacturer), \"Unknown\")\r\n| summarize Count = count() by manufacturer\r\n| order by Count desc", "size": 0, "title": "Hardware Vendor", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "piechart", "chartSettings": { "showLegend": true, "legendPosition": "bottom" } }, "customWidth": "33", "name": "node-vendor-pie", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.hybridcompute/machines\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where properties.cloudMetadata.provider == \"AzSHCI\"\r\n| where kind != \"HCI\"\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterResourceGroup = resourceGroup\r\n) on $left.resourceGroup == $right.clusterResourceGroup\r\n| extend osVersion = coalesce(tostring(properties.osVersion), \"Unknown\")\r\n| summarize Count = count() by osVersion\r\n| order by Count desc", "size": 0, "title": "OS Version", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "piechart", "chartSettings": { "showLegend": true, "legendPosition": "bottom" } }, "customWidth": "33", "name": "node-version-pie", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.hybridcompute/machines\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where properties.cloudMetadata.provider == \"AzSHCI\"\r\n| where kind != \"HCI\"\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterResourceGroup = resourceGroup\r\n) on $left.resourceGroup == $right.clusterResourceGroup\r\n| extend agentVersion = coalesce(tostring(properties.agentVersion), \"Unknown\")\r\n| summarize Count = count() by agentVersion\r\n| order by Count desc", "size": 0, "title": "Arc Agent Version", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "piechart", "chartSettings": { "showLegend": true, "legendPosition": "bottom" } }, "customWidth": "33", "name": "node-agent-version-pie", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.hybridcompute/machines\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where properties.cloudMetadata.provider == \"AzSHCI\"\r\n| where kind != \"HCI\"\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterResourceGroup = resourceGroup\r\n) on $left.resourceGroup == $right.clusterResourceGroup\r\n| extend licenseType = case(\r\n isnotnull(properties.licenseProfile.licenseType), tostring(properties.licenseProfile.licenseType),\r\n isnotnull(properties.licenseProfile.esuProfile.licenseAssignmentState), tostring(properties.licenseProfile.esuProfile.licenseAssignmentState),\r\n \"Not Configured\")\r\n| summarize Count = count() by licenseType\r\n| order by Count desc", "size": 0, "title": "License Type", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "piechart", "chartSettings": { "showLegend": true, "legendPosition": "bottom" } }, "customWidth": "33", "name": "node-license-type-pie", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 9, "content": { "version": "KqlParameterItem/1.0", "parameters": [ { "id": "machines-node-name-filter", "version": "KqlParameterItem/1.0", "name": "MachinesNodeNameFilter", "label": "Filter by Node Name", "type": 1, "description": "Optional: Filter by node name using wildcards. Example: *node01* or *hci*", "isRequired": false, "value": "", "timeContext": { "durationMs": 86400000 } }, { "id": "machines-cluster-name-filter", "version": "KqlParameterItem/1.0", "name": "MachinesClusterNameFilter", "label": "Filter by Cluster Name", "type": 1, "description": "Optional: Filter by cluster name using wildcards. Example: *prod* or *hci*", "isRequired": false, "value": "", "timeContext": { "durationMs": 86400000 } } ], "style": "pills", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces" }, "name": "machines-filters" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.hybridcompute/machines\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where properties.cloudMetadata.provider == \"AzSHCI\"\r\n| where kind != \"HCI\"\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterName = name, clusterResourceGroup = resourceGroup, clusterId = id\r\n) on $left.resourceGroup == $right.clusterResourceGroup\r\n| join kind=leftouter (\r\n extensibilityresources\r\n | where type == \"microsoft.azurestackhci/clusters/updatesummaries\"\r\n | extend updateClusterName = tostring(split(id, '/')[8])\r\n | extend currentVersion = tostring(properties.currentVersion)\r\n | extend packageVersions = properties.packageVersions\r\n | mv-expand pkg = packageVersions\r\n | where tostring(pkg.packageType) =~ 'SBE'\r\n | project updateClusterName, currentVersion, sbeVersion = tostring(pkg.version)\r\n) on $left.clusterName == $right.updateClusterName\r\n| extend nodeName = name\r\n| extend status = tostring(properties.status)\r\n| extend osSku = tostring(properties.osSku)\r\n| extend osVersion = tostring(properties.osVersion)\r\n| extend lastStatusChange = todatetime(properties.lastStatusChange)\r\n| extend logicalCoreCount = toint(properties.detectedProperties.logicalCoreCount)\r\n| extend memoryGB = toint(properties.detectedProperties.totalPhysicalMemoryInGigabytes)\r\n| extend manufacturer = tostring(properties.detectedProperties.manufacturer)\r\n| extend model = tostring(properties.detectedProperties.model)\r\n| extend processorNames = tostring(properties.detectedProperties.processorNames)\r\n| extend ipAddress = tostring(properties.networkProfile.networkInterfaces[0].ipAddresses[0].address)\r\n| extend nodeLink = strcat('https://portal.azure.com/#@/resource', id)\r\n| extend clusterLink = strcat('https://portal.azure.com/#@/resource', clusterId)\r\n| extend statusSort = case(status == 'Connected', 0, 1)\r\n| where '{MachinesNodeNameFilter}' == '' or nodeName matches regex strcat('(?i)', replace_string(replace_string('{MachinesNodeNameFilter}', '*', '.*'), '?', '.'))\r\n| where '{MachinesClusterNameFilter}' == '' or clusterName matches regex strcat('(?i)', replace_string(replace_string('{MachinesClusterNameFilter}', '*', '.*'), '?', '.'))\r\n| project nodeName, nodeLink, clusterName, clusterLink, status, osSku, osVersion, currentVersion = coalesce(currentVersion, ''), logicalCoreCount, memoryGB, manufacturer, model, processorNames, sbeVersion = coalesce(sbeVersion, ''), ipAddress, lastStatusChange, resourceGroup, subscriptionId, statusSort\r\n| order by statusSort asc, clusterName asc, nodeName asc\r\n| project-away statusSort", "size": 0, "showAnalytics": true, "title": "All Azure Local Machines", "showRefreshButton": true, "showExportToExcel": true, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "gridSettings": { "rowLimit": 2000, "formatters": [ { "columnMatch": "nodeName", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkColumn": "nodeLink" } }, { "columnMatch": "nodeLink", "formatter": 5 }, { "columnMatch": "clusterName", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkColumn": "clusterLink" } }, { "columnMatch": "clusterLink", "formatter": 5 }, { "columnMatch": "status", "formatter": 18, "formatOptions": { "thresholdsOptions": "colors", "thresholdsGrid": [ { "operator": "==", "thresholdValue": "Connected", "representation": "green", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "Disconnected", "representation": "redBright", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "Error", "representation": "orange", "text": "{0}{1}" }, { "operator": "Default", "thresholdValue": null, "representation": "gray", "text": "{0}{1}" } ] } }, { "columnMatch": "logicalCoreCount", "formatter": 0, "formatOptions": {}, "numberFormat": { "unit": 0, "options": { "style": "decimal" } } }, { "columnMatch": "memoryGB", "formatter": 0, "formatOptions": {}, "numberFormat": { "unit": 5, "options": { "style": "decimal" } } }, { "columnMatch": "lastStatusChange", "formatter": 6 } ], "filter": true, "labelSettings": [ { "columnId": "nodeName", "label": "Node Name" }, { "columnId": "nodeLink", "label": "Node" }, { "columnId": "clusterName", "label": "Cluster Name" }, { "columnId": "clusterLink", "label": "Cluster Name" }, { "columnId": "status", "label": "Status" }, { "columnId": "osSku", "label": "OS SKU" }, { "columnId": "osVersion", "label": "OS Version" }, { "columnId": "currentVersion", "label": "Cluster Version" }, { "columnId": "logicalCoreCount", "label": "vCPUs" }, { "columnId": "memoryGB", "label": "Memory" }, { "columnId": "manufacturer", "label": "Vendor" }, { "columnId": "model", "label": "Model" }, { "columnId": "processorNames", "label": "Processor" }, { "columnId": "sbeVersion", "label": "SBE Version" }, { "columnId": "ipAddress", "label": "IP Address" }, { "columnId": "lastStatusChange", "label": "Last Status Change" }, { "columnId": "resourceGroup", "label": "Resource Group" }, { "columnId": "subscriptionId", "label": "Subscription" } ] } }, "name": "all-nodes-table" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.hybridcompute/machines\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where properties.cloudMetadata.provider == \"AzSHCI\"\r\n| where kind != \"HCI\"\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterName = name, clusterResourceGroup = resourceGroup, clusterId = id\r\n | join kind=leftouter (\r\n extensibilityresources\r\n | where type == \"microsoft.azurestackhci/clusters/updatesummaries\"\r\n | extend cId = substring(id, 0, indexof(id, '/updateSummaries/'))\r\n | project cId, currentVersion = tostring(properties.currentVersion)\r\n ) on $left.clusterId == $right.cId\r\n) on $left.resourceGroup == $right.clusterResourceGroup\r\n| where tostring(properties.status) == \"Disconnected\"\r\n| extend nodeName = name\r\n| extend status = tostring(properties.status)\r\n| extend osSku = tostring(properties.osSku)\r\n| extend osVersion = tostring(properties.osVersion)\r\n| extend lastStatusChange = todatetime(properties.lastStatusChange)\r\n| extend nodeLink = strcat('https://portal.azure.com/#@/resource', id)\r\n| extend clusterLink = strcat('https://portal.azure.com/#@/resource', clusterId)\r\n| where '{MachinesNodeNameFilter}' == '' or nodeName matches regex strcat('(?i)', replace_string(replace_string('{MachinesNodeNameFilter}', '*', '.*'), '?', '.'))\r\n| where '{MachinesClusterNameFilter}' == '' or clusterName matches regex strcat('(?i)', replace_string(replace_string('{MachinesClusterNameFilter}', '*', '.*'), '?', '.'))\r\n| project nodeName, nodeLink, clusterName, clusterLink, status, osSku, osVersion, currentVersion = coalesce(currentVersion, ''), lastStatusChange, resourceGroup, subscriptionId\r\n| order by lastStatusChange asc", "size": 0, "showAnalytics": true, "title": "⚠️ Disconnected Nodes", "showRefreshButton": true, "showExportToExcel": true, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "gridSettings": { "rowLimit": 2000, "formatters": [ { "columnMatch": "nodeName", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkColumn": "nodeLink" } }, { "columnMatch": "nodeLink", "formatter": 5 }, { "columnMatch": "clusterName", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkColumn": "clusterLink" } }, { "columnMatch": "clusterLink", "formatter": 5 }, { "columnMatch": "status", "formatter": 18, "formatOptions": { "thresholdsOptions": "colors", "thresholdsGrid": [ { "operator": "==", "thresholdValue": "Disconnected", "representation": "redBright", "text": "{0}{1}" }, { "operator": "Default", "thresholdValue": null, "representation": "redBright", "text": "{0}{1}" } ] } }, { "columnMatch": "lastStatusChange", "formatter": 6 } ], "filter": true, "labelSettings": [ { "columnId": "nodeName", "label": "Node Name" }, { "columnId": "nodeLink", "label": "Node" }, { "columnId": "clusterName", "label": "Cluster Name" }, { "columnId": "clusterLink", "label": "Cluster Name" }, { "columnId": "status", "label": "Status" }, { "columnId": "osSku", "label": "OS SKU" }, { "columnId": "osVersion", "label": "OS Version" }, { "columnId": "currentVersion", "label": "Cluster Version" }, { "columnId": "lastStatusChange", "label": "Disconnected Since" }, { "columnId": "resourceGroup", "label": "Resource Group" }, { "columnId": "subscriptionId", "label": "Subscription" } ], "noDataMessage": "βœ… All nodes are connected" } }, "name": "disconnected-nodes-table" }, { "type": 1, "content": { "json": "---\r\n## 🧩 Node Extensions\r\nArc agent extensions installed on physical cluster nodes." }, "name": "text-extensions-divider" }, { "type": 9, "content": { "version": "KqlParameterItem/1.0", "parameters": [ { "id": "ext-status-filter-001", "version": "KqlParameterItem/1.0", "name": "ExtensionStatusFilter", "label": "Extension Status", "type": 2, "isRequired": true, "multiSelect": true, "quote": "'", "delimiter": ",", "typeSettings": { "additionalResourceOptions": [ "value::all" ], "showDefault": false }, "jsonData": "[\"Succeeded\", \"Failed\", \"Creating\", \"Updating\", \"Deleting\"]", "defaultValue": "value::all" }, { "id": "ext-name-filter-001", "version": "KqlParameterItem/1.0", "name": "ExtensionNameFilter", "label": "Extension Name", "type": 2, "isRequired": true, "multiSelect": true, "quote": "'", "delimiter": ",", "query": "resources\r\n| where type == \"microsoft.hybridcompute/machines/extensions\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| parse id with * \"/machines/\" mName \"/extensions/\" extName\r\n| distinct extName\r\n| order by extName asc", "crossComponentResources": [ "{Subscriptions}" ], "typeSettings": { "additionalResourceOptions": [ "value::all" ], "showDefault": false }, "defaultValue": "value::all", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources" } ], "style": "pills", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources" }, "name": "extensions-filters" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.hybridcompute/machines\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where properties.cloudMetadata.provider == \"AzSHCI\"\r\n| where kind != \"HCI\"\r\n| project machineId = id, machineName = name, machineResourceGroup = resourceGroup\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterResourceGroup = resourceGroup\r\n) on $left.machineResourceGroup == $right.clusterResourceGroup\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.hybridcompute/machines/extensions\"\r\n | where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n | extend status = tostring(properties.provisioningState)\r\n | extend extensionName = tostring(name)\r\n | parse id with * \"/machines/\" mName \"/extensions/\" *\r\n | project extensionName, status, mName, resourceGroup\r\n) on $left.machineName == $right.mName and $left.machineResourceGroup == $right.resourceGroup\r\n| where '*' in ({ExtensionNameFilter}) or extensionName in ({ExtensionNameFilter})\r\n| where '*' in ({ExtensionStatusFilter}) or status in ({ExtensionStatusFilter})\r\n| summarize count() by extensionName, status\r\n| summarize \r\n Succeeded = sumif(count_, status == \"Succeeded\"),\r\n Failed = sumif(count_, status == \"Failed\"),\r\n Creating = sumif(count_, status == \"Creating\"),\r\n Updating = sumif(count_, status == \"Updating\"),\r\n Deleting = sumif(count_, status == \"Deleting\"),\r\n Other = sumif(count_, status !in (\"Succeeded\", \"Failed\", \"Creating\", \"Updating\", \"Deleting\"))\r\n by extensionName\r\n| project extensionName, Succeeded, Failed, Creating, Updating, Deleting, Other\r\n| order by extensionName asc", "size": 0, "showRefreshButton": true, "title": "Extension Status by Type", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "gridSettings": { "rowLimit": 2000, "formatters": [ { "columnMatch": "Succeeded", "formatter": 18, "formatOptions": { "thresholdsOptions": "colors", "thresholdsGrid": [ { "operator": ">", "thresholdValue": "0", "representation": "green", "text": "{0}{1}" }, { "operator": "Default", "thresholdValue": null, "text": "{0}{1}" } ] } }, { "columnMatch": "Failed", "formatter": 18, "formatOptions": { "thresholdsOptions": "colors", "thresholdsGrid": [ { "operator": ">", "thresholdValue": "0", "representation": "redBright", "text": "{0}{1}" }, { "operator": "Default", "thresholdValue": null, "text": "{0}{1}" } ] } }, { "columnMatch": "Creating", "formatter": 18, "formatOptions": { "thresholdsOptions": "colors", "thresholdsGrid": [ { "operator": ">", "thresholdValue": "0", "representation": "blue", "text": "{0}{1}" }, { "operator": "Default", "thresholdValue": null, "text": "{0}{1}" } ] } }, { "columnMatch": "Updating", "formatter": 18, "formatOptions": { "thresholdsOptions": "colors", "thresholdsGrid": [ { "operator": ">", "thresholdValue": "0", "representation": "yellow", "text": "{0}{1}" }, { "operator": "Default", "thresholdValue": null, "text": "{0}{1}" } ] } } ], "labelSettings": [ { "columnId": "extensionName", "label": "Extension" } ] } }, "customWidth": "60", "name": "extension-status-table", "styleSettings": { "margin": "5", "padding": "5", "maxWidth": "60" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "{\"version\":\"Merge/1.0\",\"merges\":[{\"id\":\"1acf4240-2e7a-422c-8295-e53c94d43077\",\"mergeType\":\"table\",\"leftTable\":\"extension-status-table\"}],\"projectRename\":[{\"originalName\":\"[extension-status-table].extensionName\",\"mergedName\":\"extensionName\",\"fromId\":\"1acf4240-2e7a-422c-8295-e53c94d43077\"},{\"originalName\":\"[extension-status-table].Succeeded\",\"mergedName\":\"Succeeded\",\"fromId\":\"1acf4240-2e7a-422c-8295-e53c94d43077\"},{\"originalName\":\"[extension-status-table].Failed\",\"mergedName\":\"Failed\",\"fromId\":\"1acf4240-2e7a-422c-8295-e53c94d43077\"},{\"originalName\":\"[extension-status-table].Creating\",\"mergedName\":\"Creating\",\"fromId\":\"1acf4240-2e7a-422c-8295-e53c94d43077\"},{\"originalName\":\"[extension-status-table].Updating\",\"mergedName\":\"Updating\",\"fromId\":\"1acf4240-2e7a-422c-8295-e53c94d43077\"},{\"originalName\":\"[extension-status-table].Deleting\",\"mergedName\":\"Deleting\",\"fromId\":\"1acf4240-2e7a-422c-8295-e53c94d43077\"},{\"originalName\":\"[extension-status-table].Other\",\"mergedName\":\"Other\",\"fromId\":\"1acf4240-2e7a-422c-8295-e53c94d43077\"},{\"originalName\":\"[extension-status-table].Total\",\"mergedName\":\"Total\",\"fromId\":\"1acf4240-2e7a-422c-8295-e53c94d43077\"}]}", "size": 0, "title": "Extension Status Chart", "queryType": 7, "visualization": "categoricalbar", "gridSettings": { "rowLimit": 2000, "formatters": [ { "columnMatch": "Total", "formatter": 5 } ] }, "graphSettings": { "type": 0, "topContent": { "columnMatch": "extensionName", "formatter": 1 }, "centerContent": { "columnMatch": "Succeeded", "formatter": 1, "numberFormat": { "unit": 17, "options": { "maximumSignificantDigits": 3, "maximumFractionDigits": 2 } } } }, "chartSettings": { "xSettings": { "label": "Extension" }, "ySettings": { "label": "Count" } } }, "customWidth": "40", "showPin": false, "name": "extension-status-chart", "styleSettings": { "margin": "5px", "padding": "5px", "maxWidth": "40" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.hybridcompute/machines\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where properties.cloudMetadata.provider == \"AzSHCI\"\r\n| where kind != \"HCI\"\r\n| project machineId = id, machineName = name, machineResourceGroup = resourceGroup, osSku = tostring(properties.osSku), osVersion = tostring(properties.osVersion), machineStatus = tostring(properties.status)\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterName = name, clusterResourceGroup = resourceGroup, clusterId = id\r\n | join kind=leftouter (\r\n ExtensibilityResources\r\n | where type =~ 'microsoft.azurestackhci/clusters/updateSummaries'\r\n | extend cId = substring(id, 0, indexof(id, '/updateSummaries/'))\r\n | project cId, currentVersion = tostring(properties.currentVersion)\r\n ) on $left.clusterId == $right.cId\r\n) on $left.machineResourceGroup == $right.clusterResourceGroup\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.hybridcompute/machines/extensions\"\r\n | where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n | extend status = properties.provisioningState\r\n | where status == \"Failed\"\r\n | extend errorDetails = tostring(properties.instanceView.status.message)\r\n | extend errorDetailsFull = errorDetails\r\n | parse id with * \"/machines/\" mName \"/extensions/\" extensionName\r\n | project extensionName, status, errorDetails, errorDetailsFull, mName, resourceGroup, subscriptionId\r\n) on $left.machineName == $right.mName and $left.machineResourceGroup == $right.resourceGroup\r\n| extend machineLink = strcat('https://portal.azure.com/#@/resource', machineId)\r\n| extend clusterLink = strcat('https://portal.azure.com/#@/resource', clusterId)\r\n| project machineName, machineLink, extensionName, machineStatus, osSku, osVersion, clusterName, clusterLink, errorDetails, errorDetailsFull, currentVersion, resourceGroup = machineResourceGroup, subscriptionId\r\n| order by clusterName asc, machineName asc", "size": 0, "showAnalytics": true, "title": "⚠️ Failed Node Extensions", "showRefreshButton": true, "showExportToExcel": true, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "gridSettings": { "rowLimit": 2000, "formatters": [ { "columnMatch": "machineName", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkColumn": "machineLink" } }, { "columnMatch": "machineLink", "formatter": 5 }, { "columnMatch": "clusterName", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkColumn": "clusterLink" } }, { "columnMatch": "clusterLink", "formatter": 5 }, { "columnMatch": "machineStatus", "formatter": 18, "formatOptions": { "thresholdsOptions": "colors", "thresholdsGrid": [ { "operator": "==", "thresholdValue": "Connected", "representation": "green", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "Disconnected", "representation": "redBright", "text": "{0}{1}" }, { "operator": "Default", "thresholdValue": null, "representation": "gray", "text": "{0}{1}" } ] } }, { "columnMatch": "errorDetails", "formatter": 1, "formatOptions": { "linkTarget": "CellDetails", "linkIsContextBlade": true, "customColumnWidthSetting": "45ch" }, "tooltipFormat": { "tooltip": "Click to view full error details" } }, { "columnMatch": "errorDetailsFull", "formatter": 5 } ], "filter": true, "labelSettings": [ { "columnId": "machineName", "label": "Machine Name" }, { "columnId": "machineLink" }, { "columnId": "extensionName", "label": "Extension" }, { "columnId": "machineStatus", "label": "Status" }, { "columnId": "osSku", "label": "OS SKU" }, { "columnId": "osVersion", "label": "OS Version" }, { "columnId": "clusterName", "label": "Cluster Name" }, { "columnId": "clusterLink" }, { "columnId": "errorDetails", "label": "Error Details" }, { "columnId": "errorDetailsFull" }, { "columnId": "currentVersion", "label": "Cluster Version" }, { "columnId": "resourceGroup", "label": "Resource Group" }, { "columnId": "subscriptionId", "label": "Subscription" } ], "rowDetails": { "type": 1, "title": "Error Details", "content": { "json": "### ⚠️ Node Extension Error Details\r\n\r\n| Property | Value |\r\n|:--|:--|\r\n| **Machine Name** | {machineName} |\r\n| **Extension** | {extensionName} |\r\n| **Status** | {status} |\r\n| **Cluster Name** | {clusterName} |\r\n| **Cluster Version** | {currentVersion} |\r\n| **Resource Group** | {resourceGroup} |\r\n\r\n---\r\n\r\n#### Full Error Message\r\n\r\n
\r\n\r\n{errorDetailsFull}\r\n\r\n
\r\n\r\n---\r\n\r\nπŸ’‘ **Tip:** Click on the Machine Name link to view the extension in the Azure Portal for additional details and remediation options." }, "conditionalVisibility": { "parameterName": "errorDetails", "comparison": "isNotEqualTo", "value": "" } }, "noDataMessage": "βœ… No failed extensions found" } }, "name": "failed-extensions-table" }, { "type": 1, "content": { "json": "πŸ“š **Knowledge:** [Troubleshoot Azure Arc-enabled servers VM extension issues](https://learn.microsoft.com/azure/azure-arc/servers/troubleshoot-vm-extensions)" }, "name": "failed-extensions-knowledge-link" }, { "type": 1, "content": { "json": "## 🌐 Network Adapter Details\r\n\r\nNetwork adapter (NIC) information from Azure Local edge devices, including driver versions and network configuration.\r\n\r\n> **Note:** Cluster Tag filtering is not supported for this section due to Azure Resource Graph query limitations." }, "name": "text-nic-status-header" }, { "type": 9, "content": { "version": "KqlParameterItem/1.0", "parameters": [ { "id": "nic-pie-machine-filter", "version": "KqlParameterItem/1.0", "name": "NicPieMachineFilter", "label": "Machine Name", "type": 2, "isRequired": true, "multiSelect": true, "quote": "'", "delimiter": ",", "query": "resources\r\n| where type == \"microsoft.hybridcompute/machines\"\r\n| where properties.cloudMetadata.provider == \"AzSHCI\"\r\n| where kind != \"HCI\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| distinct name\r\n| order by name asc", "crossComponentResources": [ "{Subscriptions}" ], "typeSettings": { "additionalResourceOptions": [ "value::all" ], "showDefault": false }, "defaultValue": "value::all", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources" }, { "id": "nic-pie-connectivity-filter", "version": "KqlParameterItem/1.0", "name": "NicPieConnectivityFilter", "label": "Machine Connectivity", "type": 2, "isRequired": true, "multiSelect": true, "quote": "'", "delimiter": ",", "query": "resources\r\n| where type == \"microsoft.hybridcompute/machines\"\r\n| where properties.cloudMetadata.provider == \"AzSHCI\"\r\n| where kind != \"HCI\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| extend machineStatus = tostring(properties.status)\r\n| where isnotempty(machineStatus)\r\n| distinct machineStatus\r\n| order by machineStatus asc", "crossComponentResources": [ "{Subscriptions}" ], "typeSettings": { "additionalResourceOptions": [ "value::all" ], "showDefault": false }, "defaultValue": "value::all", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources" }, { "id": "nic-pie-status-filter", "version": "KqlParameterItem/1.0", "name": "NicPieStatusFilter", "label": "NIC Status", "type": 2, "isRequired": true, "multiSelect": true, "quote": "'", "delimiter": ",", "query": "extensibilityresources\r\n| where type == \"microsoft.azurestackhci/edgedevices\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| extend reportedProperties = properties.reportedProperties.networkProfile.nicDetails\r\n| mv-expand nic = reportedProperties\r\n| extend nicStatus = tostring(nic.nicStatus)\r\n| where isnotempty(nicStatus)\r\n| distinct nicStatus\r\n| order by nicStatus asc", "crossComponentResources": [ "{Subscriptions}" ], "typeSettings": { "additionalResourceOptions": [ "value::all" ], "showDefault": false }, "defaultValue": "value::all", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources" } ], "style": "pills", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources" }, "name": "nic-pie-filters" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "extensibilityresources\r\n| where type == \"microsoft.azurestackhci/edgedevices\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| extend reportedProperties = properties.reportedProperties.networkProfile.nicDetails\r\n| extend edgeDeviceRG = resourceGroup\r\n| mv-expand nic = reportedProperties\r\n| extend nicStatus = tostring(nic.nicStatus)\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.hybridcompute/machines\"\r\n | where properties.cloudMetadata.provider == \"AzSHCI\"\r\n| where kind != \"HCI\"\r\n | extend machineStatus = tostring(properties.status)\r\n | project machineName = name, machineRG = resourceGroup, machineStatus\r\n) on $left.edgeDeviceRG == $right.machineRG\r\n| where '*' in ({NicPieMachineFilter}) or machineName in ({NicPieMachineFilter})\r\n| where '*' in ({NicPieConnectivityFilter}) or machineStatus in ({NicPieConnectivityFilter})\r\n| where '*' in ({NicPieStatusFilter}) or nicStatus in ({NicPieStatusFilter})\r\n| summarize Count = count() by nicStatus\r\n| order by Count desc", "size": 0, "title": "NIC Status Distribution", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "piechart", "chartSettings": { "showLegend": true, "legendPosition": "bottom", "seriesLabelSettings": [] } }, "customWidth": "50", "name": "nic-status-pie-chart" }, { "type": 9, "content": { "version": "KqlParameterItem/1.0", "parameters": [ { "id": "nic-machine-filter", "version": "KqlParameterItem/1.0", "name": "NicMachineFilter", "label": "Machine Name", "type": 2, "isRequired": true, "multiSelect": true, "quote": "'", "delimiter": ",", "query": "resources\r\n| where type == \"microsoft.hybridcompute/machines\"\r\n| where properties.cloudMetadata.provider == \"AzSHCI\"\r\n| where kind != \"HCI\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| distinct name\r\n| order by name asc", "crossComponentResources": [ "{Subscriptions}" ], "typeSettings": { "additionalResourceOptions": [ "value::all" ], "showDefault": false }, "defaultValue": "value::all", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources" }, { "id": "nic-machine-connectivity-filter", "version": "KqlParameterItem/1.0", "name": "NicMachineConnectivityFilter", "label": "Machine Connectivity", "type": 2, "isRequired": true, "multiSelect": true, "quote": "'", "delimiter": ",", "query": "resources\r\n| where type == \"microsoft.hybridcompute/machines\"\r\n| where properties.cloudMetadata.provider == \"AzSHCI\"\r\n| where kind != \"HCI\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| extend machineStatus = tostring(properties.status)\r\n| where isnotempty(machineStatus)\r\n| distinct machineStatus\r\n| order by machineStatus asc", "crossComponentResources": [ "{Subscriptions}" ], "typeSettings": { "additionalResourceOptions": [ "value::all" ], "showDefault": false }, "defaultValue": "value::all", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources" }, { "id": "nic-status-filter", "version": "KqlParameterItem/1.0", "name": "NicStatusFilter", "label": "NIC Status", "type": 2, "isRequired": true, "multiSelect": true, "quote": "'", "delimiter": ",", "query": "extensibilityresources\r\n| where type == \"microsoft.azurestackhci/edgedevices\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| extend reportedProperties = properties.reportedProperties.networkProfile.nicDetails\r\n| mv-expand nic = reportedProperties\r\n| extend nicStatus = tostring(nic.nicStatus)\r\n| where isnotempty(nicStatus)\r\n| distinct nicStatus\r\n| order by nicStatus asc", "crossComponentResources": [ "{Subscriptions}" ], "typeSettings": { "additionalResourceOptions": [ "value::all" ], "showDefault": false }, "defaultValue": "value::all", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources" }, { "id": "nic-ip-address-filter", "version": "KqlParameterItem/1.0", "name": "NicIpAddressFilter", "label": "IP Address", "type": 1, "description": "Enter a complete or partial IP address to filter results", "value": "", "typeSettings": { "paramValidationRules": [] } } ], "style": "pills", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources" }, "name": "nic-filters" }, { "type": 1, "content": { "json": "πŸ“š **Knowledge:** [Validate and/or set the retry status of a Network ATC intent using PowerShell](https://learn.microsoft.com/windows-server/networking/network-atc/manage-network-atc#validate-automatic-remediation)" }, "name": "nic-atc-knowledge-link" }, { "type": 1, "content": { "json": "πŸ’‘ **Tip:** For any adapters with a status of 'Disconnected', first check the adapter status using `Get-NetAdapter` on the physical machine." }, "name": "nic-disconnected-tip" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "extensibilityresources\r\n| where type == \"microsoft.azurestackhci/edgedevices\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| extend deviceName = tostring(name)\r\n| extend edgeDeviceRG = resourceGroup\r\n| extend reportedProperties = properties.reportedProperties.networkProfile.nicDetails\r\n| mv-expand nic = reportedProperties\r\n| extend nicName = tostring(nic.adapterName)\r\n| extend nicStatus = tostring(nic.nicStatus)\r\n| extend driverVersion = tostring(nic.driverVersion)\r\n| extend interfaceDescription = tostring(nic.interfaceDescription)\r\n| extend nicType = case(interfaceDescription contains \"Hyper-V\", \"Virtual\", interfaceDescription contains \"Virtual\", \"Virtual\", \"Physical\")\r\n| extend ip4Address = tostring(nic.ip4Address)\r\n| extend subnetMask = tostring(nic.subnetMask)\r\n| extend defaultGateway = tostring(nic.defaultGateway)\r\n| extend dnsServers = strcat_array(nic.dnsServers, ', ')\r\n| extend macAddress = tostring(nic.macAddress)\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.hybridcompute/machines\"\r\n | where properties.cloudMetadata.provider == \"AzSHCI\"\r\n| where kind != \"HCI\"\r\n | extend parentClusterId = tostring(properties.parentClusterResourceId)\r\n | extend clusterName = tostring(split(parentClusterId, '/')[8])\r\n | extend machineStatus = tostring(properties.status)\r\n | project machineName = name, machineRG = resourceGroup, machineId = id, clusterId = parentClusterId, clusterName, machineStatus\r\n) on $left.edgeDeviceRG == $right.machineRG\r\n| where '*' in ({NicMachineFilter}) or machineName in ({NicMachineFilter})\r\n| where '*' in ({NicStatusFilter}) or nicStatus in ({NicStatusFilter})\r\n| where '*' in ({NicMachineConnectivityFilter}) or machineStatus in ({NicMachineConnectivityFilter})\r\n| where '{NicIpAddressFilter}' == '' or ip4Address contains '{NicIpAddressFilter}'\r\n| project MachineName = machineName, MachineLink = strcat('https://portal.azure.com/#@/resource', machineId), MachineConnectivity = machineStatus, ClusterName = clusterName, ClusterLink = strcat('https://portal.azure.com/#@/resource', clusterId, '/machines'), NicName = nicName, NicType = nicType, Status = nicStatus, DriverVersion = driverVersion, InterfaceDescription = interfaceDescription, IP4Address = ip4Address, SubnetMask = subnetMask, DefaultGateway = defaultGateway, DNSServers = dnsServers, MACAddress = macAddress, ResourceGroup = edgeDeviceRG\r\n| order by MachineName asc, NicType asc, NicName asc", "size": 0, "title": "All Network Adapters", "showRefreshButton": true, "showExportToExcel": true, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "gridSettings": { "rowLimit": 2000, "formatters": [ { "columnMatch": "MachineName", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkColumn": "MachineLink" } }, { "columnMatch": "MachineLink", "formatter": 5 }, { "columnMatch": "MachineConnectivity", "formatter": 18, "formatOptions": { "thresholdsOptions": "icons", "thresholdsGrid": [ { "operator": "==", "thresholdValue": "Connected", "representation": "success", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "Disconnected", "representation": "critical", "text": "{0}{1}" }, { "operator": "Default", "thresholdValue": null, "representation": "unknown", "text": "{0}{1}" } ] } }, { "columnMatch": "ClusterName", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkColumn": "ClusterLink" } }, { "columnMatch": "ClusterLink", "formatter": 5 }, { "columnMatch": "Status", "formatter": 18, "formatOptions": { "thresholdsOptions": "icons", "thresholdsGrid": [ { "operator": "==", "thresholdValue": "Up", "representation": "success", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "Disconnected", "representation": "critical", "text": "{0}{1}" }, { "operator": "Default", "thresholdValue": null, "representation": "unknown", "text": "{0}{1}" } ] } } ], "filter": true, "labelSettings": [ { "columnId": "MachineName", "label": "Machine Name" }, { "columnId": "MachineLink", "label": "Machine Link" }, { "columnId": "MachineConnectivity", "label": "Machine Connectivity" }, { "columnId": "ClusterName", "label": "Cluster Name" }, { "columnId": "ClusterLink", "label": "Cluster Link" }, { "columnId": "NicName", "label": "Adapter Name" }, { "columnId": "NicType", "label": "NIC Type" }, { "columnId": "Status", "label": "Status" }, { "columnId": "DriverVersion", "label": "Driver Version" }, { "columnId": "InterfaceDescription", "label": "Interface Description" }, { "columnId": "IP4Address", "label": "IP Address" }, { "columnId": "SubnetMask", "label": "Subnet Mask" }, { "columnId": "DefaultGateway", "label": "Default Gateway" }, { "columnId": "DNSServers", "label": "DNS Servers" }, { "columnId": "MACAddress", "label": "MAC Address" }, { "columnId": "ResourceGroup", "label": "Resource Group" } ] } }, "name": "nic-status-table" } ] }, "conditionalVisibility": { "parameterName": "selectedTab", "comparison": "isEqualTo", "value": "2" }, "name": "azure-local-nodes" }, { "type": 12, "content": { "version": "NotebookGroup/1.0", "groupType": "editable", "title": "Azure Local VMs", "items": [ { "type": 1, "content": { "json": "## πŸ’» Azure Local Virtual Machines\r\nVirtual machines running on Azure Local (Azure Stack HCI) clusters managed through Azure Arc.\r\n\r\n**Last Refreshed:** {TimeRange:label} | Click refresh on any tile to update data" }, "name": "text-vm-header" }, { "type": 11, "content": { "version": "LinkItem/1.0", "style": "nav", "links": [ { "id": "arc-vm-docs-link", "cellValue": "https://learn.microsoft.com/azure/azure-local/manage/azure-arc-vm-management-overview", "linkTarget": "Url", "linkLabel": "πŸ“š Documentation: Azure Arc VM Management", "style": "link" }, { "id": "arc-vm-troubleshoot-link", "cellValue": "https://learn.microsoft.com/azure/azure-local/manage/troubleshoot-arc-enabled-vms", "linkTarget": "Url", "linkLabel": "πŸ”§ Troubleshoot Arc-enabled VMs", "style": "link" } ] }, "name": "arc-vm-docs-link" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.hybridcompute/machines\"\r\n| where kind == \"HCI\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterRG = resourceGroup\r\n) on $left.resourceGroup == $right.clusterRG\r\n| summarize Count = count()", "size": 4, "title": "Total VMs", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "tiles", "tileSettings": { "leftContent": { "columnMatch": "Count", "formatter": 12, "formatOptions": { "palette": "blue" } }, "showBorder": true } }, "customWidth": "25", "name": "vm-total-tile", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.hybridcompute/machines\"\r\n| where kind == \"HCI\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterRG = resourceGroup\r\n) on $left.resourceGroup == $right.clusterRG\r\n| where tostring(properties.status) == \"Connected\"\r\n| summarize Count = count()", "size": 4, "title": "Connected VMs", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "tiles", "tileSettings": { "leftContent": { "columnMatch": "Count", "formatter": 12, "formatOptions": { "palette": "green" } }, "showBorder": true } }, "customWidth": "25", "name": "vm-connected-tile", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.hybridcompute/machines\"\r\n| where kind == \"HCI\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterRG = resourceGroup\r\n) on $left.resourceGroup == $right.clusterRG\r\n| extend status = tostring(properties.status)\r\n| summarize Count = count() by status\r\n| order by Count desc", "size": 0, "title": "VM Connection Status", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "piechart", "chartSettings": { "showLegend": true, "legendPosition": "bottom", "seriesLabelSettings": [] } }, "customWidth": "50", "name": "vm-status-pie", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.hybridcompute/machines\"\r\n| where kind == \"HCI\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterRG = resourceGroup\r\n) on $left.resourceGroup == $right.clusterRG\r\n| extend osSku = tostring(properties.osSku)\r\n| extend osType = iff(isempty(osSku), \"Unknown\", osSku)\r\n| summarize Count = count() by osType\r\n| order by Count desc", "size": 0, "title": "OS Distribution", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "piechart", "chartSettings": { "showLegend": true, "legendPosition": "bottom" } }, "customWidth": "50", "name": "vm-os-distribution", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.hybridcompute/machines\"\r\n| where kind == \"HCI\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterRG = resourceGroup\r\n) on $left.resourceGroup == $right.clusterRG\r\n| summarize Count = count() by resourceGroup\r\n| order by Count desc\r\n| take 10", "size": 0, "title": "VMs by Resource Group (Top 10)", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "piechart", "chartSettings": { "showLegend": true, "legendPosition": "bottom" } }, "customWidth": "50", "name": "vm-by-rg", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 1, "content": { "json": "## πŸ“ˆ VM Deployments Over Time\r\n*Based on OS installation date (properties.osInstallDate)*" }, "name": "text-vm-deployments" }, { "type": 9, "content": { "version": "KqlParameterItem/1.0", "parameters": [ { "id": "vm-deployment-months", "version": "KqlParameterItem/1.0", "name": "VMDeploymentMonths", "label": "Time Range (Months)", "type": 2, "isRequired": true, "value": "12", "typeSettings": { "additionalResourceOptions": [], "showDefault": false }, "jsonData": "[{\"value\":\"24\",\"label\":\"24 months\"},{\"value\":\"18\",\"label\":\"18 months\"},{\"value\":\"12\",\"label\":\"12 months\"},{\"value\":\"9\",\"label\":\"9 months\"},{\"value\":\"6\",\"label\":\"6 months\"},{\"value\":\"5\",\"label\":\"5 months\"},{\"value\":\"4\",\"label\":\"4 months\"},{\"value\":\"3\",\"label\":\"3 months\"},{\"value\":\"2\",\"label\":\"2 months\"},{\"value\":\"1\",\"label\":\"1 month\"}]" } ], "style": "pills", "queryType": 0, "resourceType": "microsoft.resourcegraph/resources" }, "name": "vm-deployment-params" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.hybridcompute/machines\"\r\n| where kind == \"HCI\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterRG = resourceGroup\r\n) on $left.resourceGroup == $right.clusterRG\r\n| extend osInstallDate = todatetime(properties.osInstallDate)\r\n| where isnotempty(osInstallDate)\r\n| where osInstallDate >= ago(toint('{VMDeploymentMonths}') * 30d)\r\n| extend deploymentMonth = startofmonth(osInstallDate)\r\n| summarize Count = count() by deploymentMonth\r\n| extend Series = 'VMs'\r\n| order by deploymentMonth asc", "size": 0, "title": "VM Deployments by Month (OS Install Date)", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "linechart", "chartSettings": { "xAxis": "deploymentMonth", "yAxis": [ "Count" ], "group": "Series", "seriesLabelKey": "Series", "showLegend": true } }, "customWidth": "80", "name": "vm-deployments-bar" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.hybridcompute/machines\"\r\n| where kind == \"HCI\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterRG = resourceGroup\r\n) on $left.resourceGroup == $right.clusterRG\r\n| extend osInstallDate = todatetime(properties.osInstallDate)\r\n| where isnotempty(osInstallDate)\r\n| where osInstallDate >= ago(toint('{VMDeploymentMonths}') * 30d)\r\n| extend deploymentMonth = format_datetime(osInstallDate, 'yyyy-MM')\r\n| summarize VMCount = count() by deploymentMonth\r\n| extend sortOrder = 1\r\n| union (\r\n resources\r\n | where type == \"microsoft.hybridcompute/machines\"\r\n | where kind == \"HCI\"\r\n | where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n | join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterRG = resourceGroup\r\n ) on $left.resourceGroup == $right.clusterRG\r\n | extend osInstallDate = todatetime(properties.osInstallDate)\r\n | where isnotempty(osInstallDate)\r\n | where osInstallDate >= ago(toint('{VMDeploymentMonths}') * 30d)\r\n | summarize VMCount = count()\r\n | extend deploymentMonth = 'TOTAL', sortOrder = 0\r\n)\r\n| order by sortOrder asc, deploymentMonth desc\r\n| project deploymentMonth, VMCount", "size": 1, "title": "VMs Deployed by Month", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "gridSettings": { "rowLimit": 2000, "labelSettings": [ { "columnId": "deploymentMonth", "label": "Month" }, { "columnId": "VMCount", "label": "VMs Deployed" } ] } }, "customWidth": "20", "name": "vm-deployments-table" }, { "type": 1, "content": { "json": "## πŸ’» All Azure Local VMs" }, "name": "text-vm-all" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.hybridcompute/machines\"\r\n| where kind == \"HCI\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterName = name, clusterRG = resourceGroup, clusterId = id\r\n) on $left.resourceGroup == $right.clusterRG\r\n| extend status = tostring(properties.status)\r\n| extend osSku = tostring(properties.osSku)\r\n| extend osVersion = tostring(properties.osVersion)\r\n| extend agentVersion = tostring(properties.agentVersion)\r\n| extend vCPUs = toint(properties.detectedProperties.logicalCoreCount)\r\n| extend memoryGB = toint(properties.detectedProperties.totalPhysicalMemoryInGigabytes)\r\n| extend processor = tostring(properties.detectedProperties.processorNames)\r\n| extend ipAddress = tostring(properties.networkProfile.networkInterfaces[0].ipAddresses[0].address)\r\n| extend domainName = tostring(properties.domainName)\r\n| extend osInstallDate = todatetime(properties.osInstallDate)\r\n| extend lastStatusChange = todatetime(properties.lastStatusChange)\r\n| extend vmLink = strcat('https://portal.azure.com/#@/resource', id)\r\n| extend clusterLink = strcat('https://portal.azure.com/#@/resource', clusterId)\r\n| extend statusSort = case(status == 'Connected', 0, 1)\r\n| project VMName = name, vmLink, resourceGroup, clusterName, clusterLink, status, osSku, osVersion, vCPUs, memoryGB, ipAddress, domainName, agentVersion, osInstallDate, lastStatusChange, location, statusSort\r\n| order by statusSort asc, VMName asc\r\n| project-away statusSort", "size": 0, "showAnalytics": true, "showRefreshButton": true, "showExportToExcel": true, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "gridSettings": { "rowLimit": 2000, "formatters": [ { "columnMatch": "VMName", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkColumn": "vmLink" } }, { "columnMatch": "vmLink", "formatter": 5 }, { "columnMatch": "clusterName", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkColumn": "clusterLink" } }, { "columnMatch": "clusterLink", "formatter": 5 }, { "columnMatch": "status", "formatter": 18, "formatOptions": { "thresholdsOptions": "icons", "thresholdsGrid": [ { "operator": "==", "thresholdValue": "Connected", "representation": "success", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "Disconnected", "representation": "4", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "Expired", "representation": "warning", "text": "{0}{1}" }, { "operator": "Default", "thresholdValue": null, "representation": "unknown", "text": "{0}{1}" } ] } }, { "columnMatch": "lastStatusChange", "formatter": 6 }, { "columnMatch": "osInstallDate", "formatter": 6 }, { "columnMatch": "vCPUs", "formatter": 0, "numberFormat": { "unit": 0, "options": { "style": "decimal" } } }, { "columnMatch": "memoryGB", "formatter": 0, "numberFormat": { "unit": 5, "options": { "style": "decimal" } } } ], "filter": true, "labelSettings": [ { "columnId": "VMName", "label": "VM Name" }, { "columnId": "vmLink" }, { "columnId": "resourceGroup", "label": "Resource Group" }, { "columnId": "clusterName", "label": "Cluster" }, { "columnId": "clusterLink" }, { "columnId": "status", "label": "Status" }, { "columnId": "osSku", "label": "OS SKU" }, { "columnId": "osVersion", "label": "OS Version" }, { "columnId": "vCPUs", "label": "vCPUs" }, { "columnId": "memoryGB", "label": "Memory (GB)" }, { "columnId": "ipAddress", "label": "IP Address" }, { "columnId": "domainName", "label": "Domain Name" }, { "columnId": "agentVersion", "label": "Agent Version" }, { "columnId": "osInstallDate", "label": "OS Install Date" }, { "columnId": "lastStatusChange", "label": "Last Status Change" }, { "columnId": "location", "label": "Location" } ], "noDataMessage": "No Azure Local VMs found in the selected scope" } }, "name": "vm-all-list" }, { "type": 1, "content": { "json": "## πŸ“Š VMs by Azure Local Cluster" }, "name": "text-vm-by-cluster" }, { "type": 9, "content": { "version": "KqlParameterItem/1.0", "parameters": [ { "id": "vm-cluster-name-filter", "version": "KqlParameterItem/1.0", "name": "VMClusterVMNameFilter", "label": "Filter by VM Name", "type": 1, "description": "Optional: Filter by VM name using wildcards. Example: *vm01* or *web*", "isRequired": false, "value": "", "timeContext": { "durationMs": 86400000 } }, { "id": "vm-cluster-status-filter", "version": "KqlParameterItem/1.0", "name": "VMClusterStatusFilter", "label": "VM Status", "type": 2, "description": "Filter by VM connection status", "isRequired": true, "multiSelect": true, "quote": "'", "delimiter": ",", "typeSettings": { "additionalResourceOptions": [ "value::all" ], "showDefault": false }, "jsonData": "[\"Connected\", \"Disconnected\", \"Expired\", \"Error\"]", "defaultValue": "value::all" }, { "id": "vm-cluster-ossku-filter", "version": "KqlParameterItem/1.0", "name": "VMClusterOsSkuFilter", "label": "OS SKU", "type": 2, "description": "Filter by OS SKU", "isRequired": true, "multiSelect": true, "quote": "'", "delimiter": ",", "query": "resources\r\n| where type == \"microsoft.hybridcompute/machines\"\r\n| where kind == \"HCI\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| extend osSku = tostring(properties.osSku)\r\n| where isnotempty(osSku)\r\n| distinct osSku\r\n| order by osSku asc", "crossComponentResources": [ "{Subscriptions}" ], "typeSettings": { "additionalResourceOptions": [ "value::all" ], "showDefault": false }, "defaultValue": "value::all", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources" } ], "style": "pills", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources" }, "name": "vm-by-cluster-filters" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.hybridcompute/machines\"\r\n| where kind == \"HCI\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| extend vCPUs = toint(properties.detectedProperties.logicalCoreCount)\r\n| extend memoryGB = toint(properties.detectedProperties.totalPhysicalMemoryInGigabytes)\r\n| extend ipAddress = tostring(properties.networkProfile.networkInterfaces[0].ipAddresses[0].address)\r\n| project vmName = name, vmResourceGroup = resourceGroup, vmId = id, vmStatus = tostring(properties.status), osSku = tostring(properties.osSku), vCPUs, memoryGB, ipAddress\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterName = name, clusterResourceGroup = resourceGroup, clusterId = id\r\n) on $left.vmResourceGroup == $right.clusterResourceGroup\r\n| extend vmLink = strcat('https://portal.azure.com/#@/resource', vmId)\r\n| extend clusterLink = strcat('https://portal.azure.com/#@/resource', clusterId)\r\n| where '{VMClusterVMNameFilter}' == '' or vmName matches regex strcat('(?i)', replace_string(replace_string('{VMClusterVMNameFilter}', '*', '.*'), '?', '.'))\r\n| where '*' in ({VMClusterStatusFilter}) or vmStatus in ({VMClusterStatusFilter})\r\n| where '*' in ({VMClusterOsSkuFilter}) or osSku in ({VMClusterOsSkuFilter})\r\n| project vmName, vmLink, vmStatus, osSku, vCPUs, memoryGB, ipAddress, ClusterName = coalesce(clusterName, 'Unknown'), clusterLink\r\n| order by ClusterName asc, vmName asc", "size": 0, "showAnalytics": true, "showRefreshButton": true, "showExportToExcel": true, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "gridSettings": { "rowLimit": 2000, "formatters": [ { "columnMatch": "vmName", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkColumn": "vmLink" } }, { "columnMatch": "vmLink", "formatter": 5 }, { "columnMatch": "vmStatus", "formatter": 18, "formatOptions": { "thresholdsOptions": "icons", "thresholdsGrid": [ { "operator": "==", "thresholdValue": "Connected", "representation": "success", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "Disconnected", "representation": "4", "text": "{0}{1}" }, { "operator": "Default", "thresholdValue": null, "representation": "unknown", "text": "{0}{1}" } ] } }, { "columnMatch": "ClusterName", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkColumn": "clusterLink" } }, { "columnMatch": "clusterLink", "formatter": 5 } ], "filter": true, "labelSettings": [ { "columnId": "vmName", "label": "VM Name" }, { "columnId": "vmLink" }, { "columnId": "vmStatus", "label": "VM Status" }, { "columnId": "osSku", "label": "OS SKU" }, { "columnId": "vCPUs", "label": "vCPUs" }, { "columnId": "memoryGB", "label": "Memory (GB)" }, { "columnId": "ipAddress", "label": "IP Address" }, { "columnId": "ClusterName", "label": "Cluster Name" }, { "columnId": "clusterLink" } ] } }, "name": "vm-by-cluster" }, { "type": 1, "content": { "json": "## #️⃣ VM Count by Cluster" }, "name": "text-vm-count-cluster" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.hybridcompute/machines\"\r\n| where kind == \"HCI\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| project vmName = name, vmResourceGroup = resourceGroup\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterName = name, clusterResourceGroup = resourceGroup, clusterId = id\r\n) on $left.vmResourceGroup == $right.clusterResourceGroup\r\n| summarize VMCount = count() by ClusterName = coalesce(clusterName, 'Unknown')\r\n| order by VMCount desc", "size": 0, "title": "VM Distribution by Cluster", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "barchart", "chartSettings": { "xAxis": "ClusterName", "yAxis": [ "VMCount" ], "seriesLabelSettings": [] } }, "name": "vm-count-by-cluster-bar" } ] }, "conditionalVisibility": { "parameterName": "selectedTab", "comparison": "isEqualTo", "value": "6" }, "name": "azure-local-vms" }, { "type": 12, "content": { "version": "NotebookGroup/1.0", "groupType": "editable", "title": "AKS Arc Clusters", "items": [ { "type": 1, "content": { "json": "## ☸️ AKS Arc Clusters on Azure Local\r\n\r\n**Last Refreshed:** {TimeRange:label} | Click refresh on any tile to update data\r\n\r\n> πŸ’‘ **Tip:** Tag filters do not work on this tab, if the Tag is only present on the parent Azure Local instance (and not the AKS Arc cluster itself)." }, "name": "text-aks-header" }, { "type": 11, "content": { "version": "LinkItem/1.0", "style": "nav", "links": [ { "id": "aks-arc-docs-link", "cellValue": "https://learn.microsoft.com/azure/aks/hybrid/aks-hybrid-options-overview", "linkTarget": "Url", "linkLabel": "πŸ“š Documentation: AKS on Azure Local", "style": "link" } ] }, "name": "aks-arc-docs-link" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.kubernetes/connectedclusters\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where properties.infrastructure == \"azure_stack_hci\"\r\n| extend aksResourceId = tolower(id)\r\n| join kind=leftouter (\r\n extensibilityresources\r\n | where type == \"microsoft.hybridcontainerservice/provisionedclusterinstances\"\r\n | extend customLocKey = tolower(trim_end(\"/\", tostring(extendedLocation.name)))\r\n | extend aksResourceId = tolower(substring(id, 0, indexof(id, \"/providers/Microsoft.HybridContainerService\")))\r\n | project aksResourceId, customLocKey\r\n) on aksResourceId\r\n| join kind=leftouter (\r\n resources\r\n | where type == \"microsoft.extendedlocation/customlocations\"\r\n | where tostring(properties.hostResourceId) contains \"Microsoft.ResourceConnector/appliances\"\r\n | extend arcBridgeRG = tolower(tostring(split(tostring(properties.hostResourceId), '/')[4]))\r\n | project customLocKey = tolower(trim_end(\"/\", id)), arcBridgeRG\r\n) on customLocKey\r\n| join kind=leftouter (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project azureLocalClusterName = name, clusterRG = tolower(resourceGroup)\r\n) on $left.arcBridgeRG == $right.clusterRG\r\n| where '{ClusterTagName}' == '' or isnotempty(azureLocalClusterName)\r\n| extend connectivityStatus = tostring(properties.connectivityStatus)\r\n| extend provisioningState = tostring(properties.provisioningState)\r\n| summarize \r\n Connected = countif(connectivityStatus == \"Connected\"),\r\n Offline = countif(connectivityStatus == \"Offline\"),\r\n Succeeded = countif(provisioningState == \"Succeeded\"),\r\n Failed = countif(provisioningState == \"Failed\"),\r\n Total = count()\r\n| extend Title = 'AKS Arc Clusters'", "size": 4, "title": "AKS Arc Summary", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "tiles", "tileSettings": { "titleContent": { "columnMatch": "Title", "formatter": 1 }, "leftContent": { "columnMatch": "Total", "formatter": 12, "formatOptions": { "palette": "blue" } }, "showBorder": true } }, "customWidth": "50", "name": "aks-summary-tile", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.kubernetes/connectedclusters\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where properties.infrastructure == \"azure_stack_hci\"\r\n| extend aksResourceId = tolower(id)\r\n| join kind=leftouter (\r\n extensibilityresources\r\n | where type == \"microsoft.hybridcontainerservice/provisionedclusterinstances\"\r\n | extend customLocKey = tolower(trim_end(\"/\", tostring(extendedLocation.name)))\r\n | extend aksResourceId = tolower(substring(id, 0, indexof(id, \"/providers/Microsoft.HybridContainerService\")))\r\n | project aksResourceId, customLocKey\r\n) on aksResourceId\r\n| join kind=leftouter (\r\n resources\r\n | where type == \"microsoft.extendedlocation/customlocations\"\r\n | where tostring(properties.hostResourceId) contains \"Microsoft.ResourceConnector/appliances\"\r\n | extend arcBridgeRG = tolower(tostring(split(tostring(properties.hostResourceId), '/')[4]))\r\n | project customLocKey = tolower(trim_end(\"/\", id)), arcBridgeRG\r\n) on customLocKey\r\n| join kind=leftouter (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project azureLocalClusterName = name, clusterRG = tolower(resourceGroup)\r\n) on $left.arcBridgeRG == $right.clusterRG\r\n| where '{ClusterTagName}' == '' or isnotempty(azureLocalClusterName)\r\n| extend connectivityStatus = tostring(properties.connectivityStatus)\r\n| summarize Count = count() by connectivityStatus\r\n| order by Count desc", "size": 0, "title": "Connectivity Status", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "piechart", "chartSettings": { "showLegend": true, "legendPosition": "bottom", "seriesLabelSettings": [] } }, "customWidth": "50", "name": "aks-connectivity-chart", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.kubernetes/connectedclusters\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where properties.infrastructure == \"azure_stack_hci\"\r\n| extend aksResourceId = tolower(id)\r\n| join kind=leftouter (\r\n extensibilityresources\r\n | where type == \"microsoft.hybridcontainerservice/provisionedclusterinstances\"\r\n | extend customLocKey = tolower(trim_end(\"/\", tostring(extendedLocation.name)))\r\n | extend aksResourceId = tolower(substring(id, 0, indexof(id, \"/providers/Microsoft.HybridContainerService\")))\r\n | project aksResourceId, customLocKey\r\n) on aksResourceId\r\n| join kind=leftouter (\r\n resources\r\n | where type == \"microsoft.extendedlocation/customlocations\"\r\n | where tostring(properties.hostResourceId) contains \"Microsoft.ResourceConnector/appliances\"\r\n | extend arcBridgeRG = tolower(tostring(split(tostring(properties.hostResourceId), '/')[4]))\r\n | project customLocKey = tolower(trim_end(\"/\", id)), arcBridgeRG\r\n) on customLocKey\r\n| join kind=leftouter (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project azureLocalClusterName = name, clusterRG = tolower(resourceGroup)\r\n) on $left.arcBridgeRG == $right.clusterRG\r\n| where '{ClusterTagName}' == '' or isnotempty(azureLocalClusterName)\r\n| extend kubernetesVersion = tostring(properties.kubernetesVersion)\r\n| summarize ClusterCount = count() by kubernetesVersion\r\n| order by ClusterCount desc", "size": 0, "title": "Kubernetes Version Distribution", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "piechart", "chartSettings": { "showLegend": true, "legendPosition": "bottom" } }, "customWidth": "50", "name": "aks-version-distribution", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.kubernetes/connectedclusters\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where properties.infrastructure == \"azure_stack_hci\"\r\n| extend aksResourceId = tolower(id)\r\n| join kind=leftouter (\r\n extensibilityresources\r\n | where type == \"microsoft.hybridcontainerservice/provisionedclusterinstances\"\r\n | extend customLocKey = tolower(trim_end(\"/\", tostring(extendedLocation.name)))\r\n | extend aksResourceId = tolower(substring(id, 0, indexof(id, \"/providers/Microsoft.HybridContainerService\")))\r\n | project aksResourceId, customLocKey\r\n) on aksResourceId\r\n| join kind=leftouter (\r\n resources\r\n | where type == \"microsoft.extendedlocation/customlocations\"\r\n | where tostring(properties.hostResourceId) contains \"Microsoft.ResourceConnector/appliances\"\r\n | extend arcBridgeRG = tolower(tostring(split(tostring(properties.hostResourceId), '/')[4]))\r\n | project customLocKey = tolower(trim_end(\"/\", id)), arcBridgeRG\r\n) on customLocKey\r\n| join kind=leftouter (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project azureLocalClusterName = name, clusterRG = tolower(resourceGroup)\r\n) on $left.arcBridgeRG == $right.clusterRG\r\n| where '{ClusterTagName}' == '' or isnotempty(azureLocalClusterName)\r\n| extend provisioningState = tostring(properties.provisioningState)\r\n| summarize Count = count() by provisioningState\r\n| order by Count desc", "size": 0, "title": "Provisioning State", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "piechart", "chartSettings": { "showLegend": true, "legendPosition": "bottom", "seriesLabelSettings": [] } }, "customWidth": "50", "name": "aks-provisioning-state", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 1, "content": { "json": "## πŸ“ˆ AKS Arc Cluster Deployments Over Time" }, "name": "text-aks-deployments" }, { "type": 9, "content": { "version": "KqlParameterItem/1.0", "parameters": [ { "id": "aks-deployment-months", "version": "KqlParameterItem/1.0", "name": "AKSDeploymentMonths", "label": "Time Range", "type": 2, "isRequired": true, "value": "12", "typeSettings": { "additionalResourceOptions": [], "showDefault": false }, "jsonData": "[{\"value\":\"24\",\"label\":\"24 months\"},{\"value\":\"18\",\"label\":\"18 months\"},{\"value\":\"12\",\"label\":\"12 months\"},{\"value\":\"9\",\"label\":\"9 months\"},{\"value\":\"6\",\"label\":\"6 months\"},{\"value\":\"3\",\"label\":\"3 months\"},{\"value\":\"1\",\"label\":\"1 month\"},{\"value\":\"0.5\",\"label\":\"2 weeks\"},{\"value\":\"0.25\",\"label\":\"1 week\"}]" } ], "style": "pills", "queryType": 0, "resourceType": "microsoft.resourcegraph/resources" }, "name": "aks-deployment-params" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.kubernetes/connectedclusters\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where properties.infrastructure == \"azure_stack_hci\"\r\n| extend aksResourceId = tolower(id)\r\n| join kind=leftouter (\r\n extensibilityresources\r\n | where type == \"microsoft.hybridcontainerservice/provisionedclusterinstances\"\r\n | extend customLocKey = tolower(trim_end(\"/\", tostring(extendedLocation.name)))\r\n | extend aksResourceId = tolower(substring(id, 0, indexof(id, \"/providers/Microsoft.HybridContainerService\")))\r\n | project aksResourceId, customLocKey\r\n) on aksResourceId\r\n| join kind=leftouter (\r\n resources\r\n | where type == \"microsoft.extendedlocation/customlocations\"\r\n | where tostring(properties.hostResourceId) contains \"Microsoft.ResourceConnector/appliances\"\r\n | extend arcBridgeRG = tolower(tostring(split(tostring(properties.hostResourceId), '/')[4]))\r\n | project customLocKey = tolower(trim_end(\"/\", id)), arcBridgeRG\r\n) on customLocKey\r\n| join kind=leftouter (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project azureLocalClusterName = name, clusterRG = tolower(resourceGroup)\r\n) on $left.arcBridgeRG == $right.clusterRG\r\n| where '{ClusterTagName}' == '' or isnotempty(azureLocalClusterName)\r\n| extend createdAt = todatetime(systemData.createdAt)\r\n| where isnotempty(createdAt)\r\n| extend _days = todouble('{AKSDeploymentMonths}') * 30\r\n| where createdAt >= ago(_days * 1d)\r\n| extend TimeBucket = case(\r\n _days <= 30, startofday(createdAt),\r\n _days <= 90, startofweek(createdAt),\r\n startofmonth(createdAt)\r\n)\r\n| summarize Count = count() by TimeBucket\r\n| extend Series = 'AKS Clusters'\r\n| order by TimeBucket asc", "size": 0, "title": "AKS Arc Cluster Deployments Over Time", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "linechart", "chartSettings": { "xAxis": "TimeBucket", "yAxis": [ "Count" ], "group": "Series", "showLegend": true, "seriesLabelSettings": [] } }, "customWidth": "80", "name": "aks-deployments-bar" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.kubernetes/connectedclusters\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where properties.infrastructure == \"azure_stack_hci\"\r\n| extend aksResourceId = tolower(id)\r\n| join kind=leftouter (\r\n extensibilityresources\r\n | where type == \"microsoft.hybridcontainerservice/provisionedclusterinstances\"\r\n | extend customLocKey = tolower(trim_end(\"/\", tostring(extendedLocation.name)))\r\n | extend aksResourceId = tolower(substring(id, 0, indexof(id, \"/providers/Microsoft.HybridContainerService\")))\r\n | project aksResourceId, customLocKey\r\n) on aksResourceId\r\n| join kind=leftouter (\r\n resources\r\n | where type == \"microsoft.extendedlocation/customlocations\"\r\n | where tostring(properties.hostResourceId) contains \"Microsoft.ResourceConnector/appliances\"\r\n | extend arcBridgeRG = tolower(tostring(split(tostring(properties.hostResourceId), '/')[4]))\r\n | project customLocKey = tolower(trim_end(\"/\", id)), arcBridgeRG\r\n) on customLocKey\r\n| join kind=leftouter (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project azureLocalClusterName = name, clusterRG = tolower(resourceGroup)\r\n) on $left.arcBridgeRG == $right.clusterRG\r\n| where '{ClusterTagName}' == '' or isnotempty(azureLocalClusterName)\r\n| extend createdAt = todatetime(systemData.createdAt)\r\n| where isnotempty(createdAt)\r\n| where createdAt >= ago(todouble('{AKSDeploymentMonths}') * 30d)\r\n| extend deploymentMonth = format_datetime(createdAt, 'yyyy-MM')\r\n| summarize ClusterCount = count() by deploymentMonth\r\n| extend sortOrder = 1\r\n| union (\r\n resources\r\n | where type == \"microsoft.kubernetes/connectedclusters\"\r\n | where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n | where properties.infrastructure == \"azure_stack_hci\"\r\n | extend createdAt = todatetime(systemData.createdAt)\r\n | where isnotempty(createdAt)\r\n | where createdAt >= ago(todouble('{AKSDeploymentMonths}') * 30d)\r\n | summarize ClusterCount = count()\r\n | extend deploymentMonth = 'TOTAL', sortOrder = 0\r\n)\r\n| order by sortOrder asc, deploymentMonth desc\r\n| project deploymentMonth, ClusterCount", "size": 1, "title": "AKS Arc Clusters Deployed by Month", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "gridSettings": { "rowLimit": 2000, "labelSettings": [ { "columnId": "deploymentMonth", "label": "Month" }, { "columnId": "ClusterCount", "label": "Clusters Deployed" } ] } }, "customWidth": "20", "name": "aks-deployments-table" }, { "type": 1, "content": { "json": "## ☸️ All AKS Arc Clusters" }, "name": "text-aks-all" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.kubernetes/connectedclusters\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where properties.infrastructure == \"azure_stack_hci\"\r\n| extend connectivityStatus = tostring(properties.connectivityStatus)\r\n| extend provisioningState = tostring(properties.provisioningState)\r\n| extend kubernetesVersion = tostring(properties.kubernetesVersion)\r\n| extend agentVersion = tostring(properties.agentVersion)\r\n| extend totalNodeCount = toint(properties.totalNodeCount)\r\n| extend totalCoreCount = toint(properties.totalCoreCount)\r\n| extend distribution = tostring(properties.distribution)\r\n| extend oidcIssuerUrl = tostring(properties.oidcIssuerProfile.issuerUrl)\r\n| extend adminGroupObjectIds = strcat_array(coalesce(properties.aadProfile.adminGroupObjectIDs, dynamic([])), ', ')\r\n| extend lastConnectivityTime = todatetime(properties.lastConnectivityTime)\r\n| extend certExpiration = todatetime(properties.managedIdentityCertificateExpirationTime)\r\n| extend createdAt = todatetime(systemData.createdAt)\r\n| extend clusterLink = strcat('https://portal.azure.com/#@/resource', id)\r\n| extend clusterResourceId = tolower(id)\r\n| join kind=leftouter (\r\n extensibilityresources\r\n | where type == \"microsoft.hybridcontainerservice/provisionedclusterinstances\"\r\n | extend customLocId = tolower(tostring(extendedLocation.name))\r\n | extend customLocKey = iff(customLocId startswith \"/\", tolower(trim_end(\"/\", customLocId)), customLocId)\r\n | extend controlPlaneNodes = toint(properties[\"controlPlane\"][\"count\"])\r\n | mv-expand agentPool = properties.agentPoolProfiles\r\n | summarize customLocKey = max(customLocKey), controlPlaneNodes = max(controlPlaneNodes), workerNodes = sum(toint(agentPool[\"count\"])) by id\r\n | extend clusterResourceId = tolower(substring(id, 0, indexof(id, \"/providers/Microsoft.HybridContainerService\")))\r\n | project clusterResourceId, customLocKey, controlPlaneNodes, workerNodes\r\n) on clusterResourceId\r\n| project ClusterName = name, clusterLink, clusterResourceId, customLocKey, connectivityStatus, provisioningState, resourceGroup, kubernetesVersion, agentVersion, controlPlaneNodes, workerNodes, totalNodeCount, totalCoreCount, distribution, oidcIssuerUrl, adminGroupObjectIds, lastConnectivityTime, certExpiration, createdAt, location\r\n| order by connectivityStatus asc, ClusterName asc", "size": 0, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ] }, "conditionalVisibility": { "parameterName": "neverVisible", "comparison": "isEqualTo", "value": "alwaysHidden" }, "name": "aks-all-clusters-base" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.extendedlocation/customlocations\"\r\n| where tostring(properties.hostResourceId) contains \"Microsoft.ResourceConnector/appliances\"\r\n| extend arcBridgeRG = tolower(tostring(split(tostring(properties.hostResourceId), '/')[4]))\r\n| extend customLocKey = tolower(trim_end(\"/\", id))\r\n| join kind=leftouter (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project azureLocalClusterName = name, clusterRG = tolower(resourceGroup), azureLocalClusterLink = strcat('https://portal.azure.com/#@/resource', id)\r\n) on $left.arcBridgeRG == $right.clusterRG\r\n| project customLocKey, azureLocalClusterName, azureLocalClusterLink", "size": 0, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ] }, "conditionalVisibility": { "parameterName": "neverVisible", "comparison": "isEqualTo", "value": "alwaysHidden" }, "name": "aks-azurelocal-mapping" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "{\"version\":\"Merge/1.0\",\"merges\":[{\"id\":\"aks-merge\",\"mergeType\":\"leftouter\",\"leftTable\":\"aks-all-clusters-base\",\"rightTable\":\"aks-azurelocal-mapping\",\"leftColumn\":\"customLocKey\",\"rightColumn\":\"customLocKey\"}],\"projectRename\":[{\"originalName\":\"[aks-all-clusters-base].ClusterName\",\"mergedName\":\"ClusterName\",\"fromId\":\"aks-merge\"},{\"originalName\":\"[aks-all-clusters-base].clusterLink\",\"mergedName\":\"clusterLink\",\"fromId\":\"aks-merge\"},{\"originalName\":\"[aks-all-clusters-base].connectivityStatus\",\"mergedName\":\"connectivityStatus\",\"fromId\":\"aks-merge\"},{\"originalName\":\"[aks-all-clusters-base].provisioningState\",\"mergedName\":\"provisioningState\",\"fromId\":\"aks-merge\"},{\"originalName\":\"[aks-all-clusters-base].resourceGroup\",\"mergedName\":\"resourceGroup\",\"fromId\":\"aks-merge\"},{\"originalName\":\"[aks-azurelocal-mapping].azureLocalClusterName\",\"mergedName\":\"azureLocalClusterName\",\"fromId\":\"aks-merge\"},{\"originalName\":\"[aks-azurelocal-mapping].azureLocalClusterLink\",\"mergedName\":\"azureLocalClusterLink\",\"fromId\":\"aks-merge\"},{\"originalName\":\"[aks-all-clusters-base].kubernetesVersion\",\"mergedName\":\"kubernetesVersion\",\"fromId\":\"aks-merge\"},{\"originalName\":\"[aks-all-clusters-base].agentVersion\",\"mergedName\":\"agentVersion\",\"fromId\":\"aks-merge\"},{\"originalName\":\"[aks-all-clusters-base].controlPlaneNodes\",\"mergedName\":\"controlPlaneNodes\",\"fromId\":\"aks-merge\"},{\"originalName\":\"[aks-all-clusters-base].workerNodes\",\"mergedName\":\"workerNodes\",\"fromId\":\"aks-merge\"},{\"originalName\":\"[aks-all-clusters-base].totalNodeCount\",\"mergedName\":\"totalNodeCount\",\"fromId\":\"aks-merge\"},{\"originalName\":\"[aks-all-clusters-base].totalCoreCount\",\"mergedName\":\"totalCoreCount\",\"fromId\":\"aks-merge\"},{\"originalName\":\"[aks-all-clusters-base].distribution\",\"mergedName\":\"distribution\",\"fromId\":\"aks-merge\"},{\"originalName\":\"[aks-all-clusters-base].oidcIssuerUrl\",\"mergedName\":\"oidcIssuerUrl\",\"fromId\":\"aks-merge\"},{\"originalName\":\"[aks-all-clusters-base].adminGroupObjectIds\",\"mergedName\":\"adminGroupObjectIds\",\"fromId\":\"aks-merge\"},{\"originalName\":\"[aks-all-clusters-base].lastConnectivityTime\",\"mergedName\":\"lastConnectivityTime\",\"fromId\":\"aks-merge\"},{\"originalName\":\"[aks-all-clusters-base].certExpiration\",\"mergedName\":\"certExpiration\",\"fromId\":\"aks-merge\"},{\"originalName\":\"[aks-all-clusters-base].createdAt\",\"mergedName\":\"createdAt\",\"fromId\":\"aks-merge\"},{\"originalName\":\"[aks-all-clusters-base].location\",\"mergedName\":\"location\",\"fromId\":\"aks-merge\"},{\"originalName\":\"[aks-all-clusters-base].clusterResourceId\"},{\"originalName\":\"[aks-all-clusters-base].customLocKey\"},{\"originalName\":\"[aks-azurelocal-mapping].customLocKey\"}]}", "size": 0, "showAnalytics": true, "showRefreshButton": true, "showExportToExcel": true, "queryType": 7, "gridSettings": { "rowLimit": 2000, "formatters": [ { "columnMatch": "ClusterName", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkColumn": "clusterLink" } }, { "columnMatch": "clusterLink", "formatter": 5 }, { "columnMatch": "connectivityStatus", "formatter": 18, "formatOptions": { "thresholdsOptions": "icons", "thresholdsGrid": [ { "operator": "==", "thresholdValue": "Connected", "representation": "success", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "Offline", "representation": "4", "text": "{0}{1}" }, { "operator": "Default", "thresholdValue": null, "representation": "warning", "text": "{0}{1}" } ] } }, { "columnMatch": "provisioningState", "formatter": 18, "formatOptions": { "thresholdsOptions": "icons", "thresholdsGrid": [ { "operator": "==", "thresholdValue": "Succeeded", "representation": "success", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "Failed", "representation": "4", "text": "{0}{1}" }, { "operator": "Default", "thresholdValue": null, "representation": "pending", "text": "{0}{1}" } ] } }, { "columnMatch": "azureLocalClusterName", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkColumn": "azureLocalClusterLink" } }, { "columnMatch": "azureLocalClusterLink", "formatter": 5 }, { "columnMatch": "lastConnectivityTime", "formatter": 6 }, { "columnMatch": "certExpiration", "formatter": 6 }, { "columnMatch": "createdAt", "formatter": 6 } ], "filter": true, "labelSettings": [ { "columnId": "ClusterName", "label": "AKS Cluster Name" }, { "columnId": "clusterLink" }, { "columnId": "resourceGroup", "label": "Resource Group" }, { "columnId": "azureLocalClusterName", "label": "Azure Local Cluster" }, { "columnId": "azureLocalClusterLink" }, { "columnId": "connectivityStatus", "label": "Connectivity Status" }, { "columnId": "provisioningState", "label": "Provisioning State" }, { "columnId": "kubernetesVersion", "label": "Kubernetes Version" }, { "columnId": "agentVersion", "label": "Agent Version" }, { "columnId": "controlPlaneNodes", "label": "Control Plane" }, { "columnId": "workerNodes", "label": "Workers" }, { "columnId": "totalNodeCount", "label": "Total Nodes" }, { "columnId": "totalCoreCount", "label": "Total Core Count" }, { "columnId": "distribution", "label": "Distribution" }, { "columnId": "oidcIssuerUrl", "label": "OIDC Issuer URL" }, { "columnId": "adminGroupObjectIds", "label": "Admin Group Object IDs" }, { "columnId": "lastConnectivityTime", "label": "Last Connectivity Time" }, { "columnId": "certExpiration", "label": "Certificate Expiration" }, { "columnId": "createdAt", "label": "Created At" }, { "columnId": "location", "label": "Location" } ] } }, "name": "aks-all-clusters" }, { "type": 1, "content": { "json": "---\r\n## 🌐 AKS Arc Network Details" }, "name": "text-aks-network-details" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.kubernetes/connectedclusters\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where properties.infrastructure == \"azure_stack_hci\"\r\n| extend clusterLink = strcat('https://portal.azure.com/#@/resource', id)\r\n| extend clusterResourceId = tolower(id)\r\n| project ClusterName = name, clusterLink, clusterResourceId, resourceGroup, location\r\n| join kind=leftouter (\r\n extensibilityresources\r\n | where type == \"microsoft.hybridcontainerservice/provisionedclusterinstances\"\r\n | extend clusterResourceId = tolower(substring(id, 0, indexof(id, \"/providers/Microsoft.HybridContainerService\")))\r\n | extend podCidr = tostring(properties.networkProfile.podCidr)\r\n | extend controlPlaneHostIP = tostring(properties.controlPlane.controlPlaneEndpoint.hostIP)\r\n | extend vnetSubnetIds = properties.cloudProviderProfile.infraNetworkProfile.vnetSubnetIds\r\n | mv-expand vnetSubnetId = vnetSubnetIds\r\n | extend logicalNetworkId = tolower(tostring(vnetSubnetId))\r\n | summarize podCidr = max(podCidr), controlPlaneHostIP = max(controlPlaneHostIP), logicalNetworkIds = make_set(logicalNetworkId) by clusterResourceId\r\n | project clusterResourceId, podCidr, controlPlaneHostIP, logicalNetworkIds\r\n) on clusterResourceId\r\n| mv-expand logicalNetworkId = logicalNetworkIds\r\n| extend logicalNetworkId = tostring(logicalNetworkId)\r\n| join kind=leftouter (\r\n resources\r\n | where type == \"microsoft.azurestackhci/logicalnetworks\"\r\n | extend logicalNetworkId = tolower(id)\r\n | extend logicalNetworkLink = strcat('https://portal.azure.com/#@/resource', id)\r\n | extend logicalNetworkState = tostring(properties.status.provisioningStatus.status)\r\n | extend subnets = properties.subnets\r\n | mv-expand subnet = subnets\r\n | extend addressPrefix = tostring(subnet.properties.addressPrefix)\r\n | extend vlanId = tostring(subnet.properties.vlan)\r\n | extend ipPools = subnet.properties.ipPools\r\n | mv-expand ipPool = ipPools\r\n | extend ipPoolStart = tostring(ipPool.start)\r\n | extend ipPoolEnd = tostring(ipPool.['end'])\r\n | extend ipPoolUsed = tostring(ipPool.info.used)\r\n | extend ipPoolAvailable = tostring(ipPool.info.available)\r\n | extend dnsServers = strcat_array(properties.dhcpOptions.dnsServers, ', ')\r\n | project logicalNetworkId, logicalNetworkName = name, logicalNetworkLink, logicalNetworkState, addressPrefix, vlanId, ipPoolStart, ipPoolEnd, ipPoolUsed, ipPoolAvailable, dnsServers\r\n) on logicalNetworkId\r\n| project ClusterName, clusterLink, clusterResourceId, resourceGroup, location, podCidr, controlPlaneHostIP, logicalNetworkName, logicalNetworkLink, logicalNetworkState, addressPrefix, ipPoolStart, ipPoolEnd, ipPoolUsed, ipPoolAvailable, vlanId, dnsServers\r\n| order by ClusterName asc", "size": 0, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ] }, "conditionalVisibility": { "parameterName": "alwaysHidden", "comparison": "isEqualTo", "value": "true" }, "name": "aks-network-base" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "extensibilityresources\r\n| where type == \"microsoft.kubernetesruntime/loadbalancers\"\r\n| extend clusterResourceId = tolower(substring(id, 0, indexof(id, \"/providers/Microsoft.KubernetesRuntime\")))\r\n| summarize loadBalancers = strcat_array(make_set(name), ', ') by clusterResourceId", "size": 0, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ] }, "conditionalVisibility": { "parameterName": "alwaysHidden", "comparison": "isEqualTo", "value": "true" }, "name": "aks-loadbalancers-lookup" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "{\"version\":\"Merge/1.0\",\"merges\":[{\"id\":\"aks-network-merge\",\"mergeType\":\"leftouter\",\"leftTable\":\"aks-network-base\",\"rightTable\":\"aks-loadbalancers-lookup\",\"leftColumn\":\"clusterResourceId\",\"rightColumn\":\"clusterResourceId\"}],\"projectRename\":[{\"originalName\":\"[aks-network-base].ClusterName\",\"mergedName\":\"ClusterName\",\"fromId\":\"aks-network-merge\"},{\"originalName\":\"[aks-network-base].clusterLink\",\"mergedName\":\"clusterLink\",\"fromId\":\"aks-network-merge\"},{\"originalName\":\"[aks-network-base].resourceGroup\",\"mergedName\":\"resourceGroup\",\"fromId\":\"aks-network-merge\"},{\"originalName\":\"[aks-network-base].location\",\"mergedName\":\"location\",\"fromId\":\"aks-network-merge\"},{\"originalName\":\"[aks-network-base].podCidr\",\"mergedName\":\"podCidr\",\"fromId\":\"aks-network-merge\"},{\"originalName\":\"[aks-network-base].controlPlaneHostIP\",\"mergedName\":\"controlPlaneHostIP\",\"fromId\":\"aks-network-merge\"},{\"originalName\":\"[aks-network-base].logicalNetworkName\",\"mergedName\":\"logicalNetworkName\",\"fromId\":\"aks-network-merge\"},{\"originalName\":\"[aks-network-base].logicalNetworkLink\",\"mergedName\":\"logicalNetworkLink\",\"fromId\":\"aks-network-merge\"},{\"originalName\":\"[aks-network-base].logicalNetworkState\",\"mergedName\":\"logicalNetworkState\",\"fromId\":\"aks-network-merge\"},{\"originalName\":\"[aks-loadbalancers-lookup].loadBalancers\",\"mergedName\":\"loadBalancers\",\"fromId\":\"aks-network-merge\"},{\"originalName\":\"[aks-network-base].addressPrefix\",\"mergedName\":\"addressPrefix\",\"fromId\":\"aks-network-merge\"},{\"originalName\":\"[aks-network-base].ipPoolStart\",\"mergedName\":\"ipPoolStart\",\"fromId\":\"aks-network-merge\"},{\"originalName\":\"[aks-network-base].ipPoolEnd\",\"mergedName\":\"ipPoolEnd\",\"fromId\":\"aks-network-merge\"},{\"originalName\":\"[aks-network-base].ipPoolUsed\",\"mergedName\":\"ipPoolUsed\",\"fromId\":\"aks-network-merge\"},{\"originalName\":\"[aks-network-base].ipPoolAvailable\",\"mergedName\":\"ipPoolAvailable\",\"fromId\":\"aks-network-merge\"},{\"originalName\":\"[aks-network-base].vlanId\",\"mergedName\":\"vlanId\",\"fromId\":\"aks-network-merge\"},{\"originalName\":\"[aks-network-base].dnsServers\",\"mergedName\":\"dnsServers\",\"fromId\":\"aks-network-merge\"},{\"originalName\":\"[aks-network-base].clusterResourceId\"},{\"originalName\":\"[aks-loadbalancers-lookup].clusterResourceId\"}]}", "size": 0, "showAnalytics": true, "showRefreshButton": true, "showExportToExcel": true, "queryType": 7, "gridSettings": { "rowLimit": 2000, "formatters": [ { "columnMatch": "ClusterName", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkColumn": "clusterLink" } }, { "columnMatch": "clusterLink", "formatter": 5 }, { "columnMatch": "logicalNetworkName", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkColumn": "logicalNetworkLink" } }, { "columnMatch": "logicalNetworkLink", "formatter": 5 }, { "columnMatch": "logicalNetworkState", "formatter": 18, "formatOptions": { "thresholdsOptions": "icons", "thresholdsGrid": [ { "operator": "==", "thresholdValue": "Succeeded", "representation": "success", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "Failed", "representation": "4", "text": "{0}{1}" }, { "operator": "Default", "thresholdValue": null, "representation": "pending", "text": "{0}{1}" } ] } } ], "filter": true, "labelSettings": [ { "columnId": "ClusterName", "label": "AKS Cluster Name" }, { "columnId": "clusterLink" }, { "columnId": "resourceGroup", "label": "Resource Group" }, { "columnId": "location", "label": "Location" }, { "columnId": "podCidr", "label": "Pod CIDR" }, { "columnId": "controlPlaneHostIP", "label": "Control Plane IP" }, { "columnId": "logicalNetworkName", "label": "Logical Network" }, { "columnId": "logicalNetworkLink" }, { "columnId": "logicalNetworkState", "label": "Network State" }, { "columnId": "loadBalancers", "label": "Load Balancers" }, { "columnId": "addressPrefix", "label": "Address Prefix" }, { "columnId": "ipPoolStart", "label": "IP Pool Start" }, { "columnId": "ipPoolEnd", "label": "IP Pool End" }, { "columnId": "ipPoolUsed", "label": "IPs Used" }, { "columnId": "ipPoolAvailable", "label": "IPs Available" }, { "columnId": "vlanId", "label": "VLAN ID" }, { "columnId": "dnsServers", "label": "DNS Servers" } ] } }, "name": "aks-network-details" }, { "type": 1, "content": { "json": "---\r\n## βš–οΈ Load Balancers" }, "name": "text-aks-load-balancers" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "extensibilityresources\r\n| where type == \"microsoft.kubernetesruntime/loadbalancers\"\r\n| extend clusterResourceId = tolower(substring(id, 0, indexof(id, \"/providers/Microsoft.KubernetesRuntime\")))\r\n| extend loadBalancerName = name\r\n| extend provisioningState = tostring(properties.provisioningState)\r\n| extend advertiseMode = tostring(properties.advertiseMode)\r\n| extend addresses = properties.addresses\r\n| mv-expand addr = addresses\r\n| extend addressValue = tostring(addr)\r\n| summarize addressList = strcat_array(make_set(addressValue), ', ') by clusterResourceId, loadBalancerName, provisioningState, advertiseMode\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.kubernetes/connectedclusters\"\r\n | where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n | where properties.infrastructure == \"azure_stack_hci\"\r\n | extend clusterLink = strcat('https://portal.azure.com/#@/resource', id)\r\n | project ClusterName = name, clusterLink, clusterResourceId = tolower(id), resourceGroup\r\n) on clusterResourceId\r\n| project ClusterName, clusterLink, resourceGroup, loadBalancerName, provisioningState, advertiseMode, addressList\r\n| order by ClusterName asc, loadBalancerName asc", "size": 0, "showAnalytics": true, "showRefreshButton": true, "showExportToExcel": true, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "gridSettings": { "rowLimit": 2000, "formatters": [ { "columnMatch": "ClusterName", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkColumn": "clusterLink" } }, { "columnMatch": "clusterLink", "formatter": 5 }, { "columnMatch": "provisioningState", "formatter": 18, "formatOptions": { "thresholdsOptions": "icons", "thresholdsGrid": [ { "operator": "==", "thresholdValue": "Succeeded", "representation": "success", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "Failed", "representation": "4", "text": "{0}{1}" }, { "operator": "Default", "thresholdValue": null, "representation": "pending", "text": "{0}{1}" } ] } } ], "filter": true, "labelSettings": [ { "columnId": "ClusterName", "label": "AKS Cluster Name" }, { "columnId": "clusterLink" }, { "columnId": "resourceGroup", "label": "Resource Group" }, { "columnId": "loadBalancerName", "label": "Load Balancer" }, { "columnId": "provisioningState", "label": "Provisioning State" }, { "columnId": "advertiseMode", "label": "Advertise Mode" }, { "columnId": "addressList", "label": "Addresses" } ] } }, "name": "aks-load-balancers" }, { "type": 1, "content": { "json": "---\r\n## πŸ“¦ Kubernetes Version & Upgrade Status" }, "name": "text-aks-upgrade-status" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.kubernetes/connectedclusters\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where properties.infrastructure == \"azure_stack_hci\"\r\n| extend clusterResourceId = tolower(id)\r\n| join kind=leftouter (\r\n extensibilityresources\r\n | where type == \"microsoft.hybridcontainerservice/provisionedclusterinstances/upgradeprofiles\"\r\n | extend clusterResourceId = tolower(substring(id, 0, indexof(id, \"/providers/Microsoft.HybridContainerService\")))\r\n | extend currentVersion = coalesce(tostring(properties.controlPlaneProfile.kubernetesVersion), tostring(properties.currentKubernetesVersion), tostring(properties.currentVersion), tostring(properties.kubernetesVersion))\r\n | extend upgradesRaw = coalesce(properties.controlPlaneProfile.upgrades, properties.upgrades)\r\n | extend upgradesRaw = iif(isnull(upgradesRaw) or array_length(upgradesRaw) == 0, dynamic([{}]), upgradesRaw)\r\n | mv-expand upgrade = upgradesRaw\r\n | extend upgradeVersion = coalesce(tostring(upgrade.kubernetesVersion), tostring(upgrade.version), tostring(upgrade.targetVersion))\r\n | extend curParts = split(currentVersion, '.')\r\n | extend upParts = split(upgradeVersion, '.')\r\n | extend curMajor = toint(curParts[0]), curMinor = toint(curParts[1])\r\n | extend upMajor = toint(upParts[0]), upMinor = toint(upParts[1])\r\n | extend isMinorUpgrade = iif(isnotempty(upgradeVersion) and (upMajor > curMajor or (upMajor == curMajor and upMinor > curMinor)), 1, 0)\r\n | summarize hasMinorUpgrade = max(isMinorUpgrade) by clusterResourceId\r\n | project clusterResourceId, hasMinorUpgrade\r\n) on clusterResourceId\r\n| extend status = case(hasMinorUpgrade == 1, 'Minor available', 'Fully upgraded')\r\n| summarize Count = count() by status", "size": 3, "title": "Upgrade Status (Minor Available vs Fully Upgraded)", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "piechart", "chartSettings": { "seriesLabelSettings": [] } }, "customWidth": "50", "name": "aks-upgrade-status-pie" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.kubernetes/connectedclusters\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where properties.infrastructure == \"azure_stack_hci\"\r\n| extend clusterResourceId = tolower(id)\r\n| join kind=leftouter (\r\n extensibilityresources\r\n | where type == \"microsoft.hybridcontainerservice/provisionedclusterinstances/upgradeprofiles\"\r\n | extend clusterResourceId = tolower(substring(id, 0, indexof(id, \"/providers/Microsoft.HybridContainerService\")))\r\n | extend currentVersion = coalesce(tostring(properties.controlPlaneProfile.kubernetesVersion), tostring(properties.currentKubernetesVersion), tostring(properties.currentVersion), tostring(properties.kubernetesVersion))\r\n | extend upgradesRaw = coalesce(properties.controlPlaneProfile.upgrades, properties.upgrades)\r\n | extend upgradesRaw = iif(isnull(upgradesRaw) or array_length(upgradesRaw) == 0, dynamic([{}]), upgradesRaw)\r\n | mv-expand upgrade = upgradesRaw\r\n | extend upgradeVersion = coalesce(tostring(upgrade.kubernetesVersion), tostring(upgrade.version), tostring(upgrade.targetVersion))\r\n | summarize currentVersion = any(currentVersion), upgrades = strcat_array(make_set_if(upgradeVersion, isnotempty(upgradeVersion)), ', ') by clusterResourceId\r\n) on clusterResourceId\r\n| extend upgrades = iif(isempty(upgrades), 'No upgrades', upgrades)\r\n| summarize ClusterCount = count() by currentVersion, upgrades\r\n| order by currentVersion asc, upgrades asc", "size": 0, "title": "Version/Upgrade Summary", "showRefreshButton": true, "showExportToExcel": true, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "gridSettings": { "rowLimit": 2000, "labelSettings": [ { "columnId": "currentVersion", "label": "Current Version" }, { "columnId": "upgrades", "label": "Available Upgrades" }, { "columnId": "ClusterCount", "label": "Clusters" } ] } }, "customWidth": "50", "name": "aks-version-upgrade-summary" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.kubernetes/connectedclusters\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where properties.infrastructure == \"azure_stack_hci\"\r\n| extend clusterResourceId = tolower(id)\r\n| extend clusterLink = strcat('https://portal.azure.com/#@/resource', id)\r\n| project clusterResourceId, ClusterName = name, clusterLink, resourceGroup, location\r\n| join kind=leftouter (\r\n extensibilityresources\r\n | where type == \"microsoft.hybridcontainerservice/provisionedclusterinstances/upgradeprofiles\"\r\n | extend clusterResourceId = tolower(substring(id, 0, indexof(id, \"/providers/Microsoft.HybridContainerService\")))\r\n | extend currentVersion = coalesce(tostring(properties.controlPlaneProfile.kubernetesVersion), tostring(properties.currentKubernetesVersion), tostring(properties.currentVersion), tostring(properties.kubernetesVersion))\r\n | extend upgradesRaw = coalesce(properties.controlPlaneProfile.upgrades, properties.upgrades)\r\n | extend upgradesRaw = iif(isnull(upgradesRaw) or array_length(upgradesRaw) == 0, dynamic([{}]), upgradesRaw)\r\n | mv-expand upgrade = upgradesRaw\r\n | extend upgradeVersion = coalesce(tostring(upgrade.kubernetesVersion), tostring(upgrade.version), tostring(upgrade.targetVersion))\r\n | summarize currentVersion = any(currentVersion), upgrades = strcat_array(make_set_if(upgradeVersion, isnotempty(upgradeVersion)), ', ') by clusterResourceId\r\n) on clusterResourceId\r\n| project ClusterName, clusterLink, resourceGroup, location, currentVersion, upgrades\r\n| order by ClusterName asc", "size": 0, "title": "Kubernetes Version and Available Upgrades", "showAnalytics": true, "showRefreshButton": true, "showExportToExcel": true, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "gridSettings": { "rowLimit": 2000, "formatters": [ { "columnMatch": "ClusterName", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkColumn": "clusterLink" } }, { "columnMatch": "clusterLink", "formatter": 5 } ], "filter": true, "labelSettings": [ { "columnId": "ClusterName", "label": "AKS Cluster Name" }, { "columnId": "clusterLink" }, { "columnId": "resourceGroup", "label": "Resource Group" }, { "columnId": "location", "label": "Location" }, { "columnId": "currentVersion", "label": "Current Version" }, { "columnId": "upgrades", "label": "Available Upgrades" } ] } }, "name": "aks-version-upgrades-detail" }, { "type": 1, "content": { "json": "## πŸ” Certificate Expiration Warning\r\n*Clusters with managed identity certificates expiring within 30 days*" }, "name": "text-cert-warning" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.kubernetes/connectedclusters\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where properties.infrastructure == \"azure_stack_hci\"\r\n| extend aksResourceId = tolower(id)\r\n| join kind=leftouter (\r\n extensibilityresources\r\n | where type == \"microsoft.hybridcontainerservice/provisionedclusterinstances\"\r\n | extend customLocKey = tolower(trim_end(\"/\", tostring(extendedLocation.name)))\r\n | extend aksResourceId = tolower(substring(id, 0, indexof(id, \"/providers/Microsoft.HybridContainerService\")))\r\n | project aksResourceId, customLocKey\r\n) on aksResourceId\r\n| join kind=leftouter (\r\n resources\r\n | where type == \"microsoft.extendedlocation/customlocations\"\r\n | where tostring(properties.hostResourceId) contains \"Microsoft.ResourceConnector/appliances\"\r\n | extend arcBridgeRG = tolower(tostring(split(tostring(properties.hostResourceId), '/')[4]))\r\n | project customLocKey = tolower(trim_end(\"/\", id)), arcBridgeRG\r\n) on customLocKey\r\n| join kind=leftouter (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project azureLocalClusterName = name, clusterRG = tolower(resourceGroup)\r\n) on $left.arcBridgeRG == $right.clusterRG\r\n| where '{ClusterTagName}' == '' or isnotempty(azureLocalClusterName)\r\n| extend certExpiration = todatetime(properties.managedIdentityCertificateExpirationTime)\r\n| where isnotempty(certExpiration)\r\n| where certExpiration <= now() + 30d\r\n| extend daysUntilExpiration = datetime_diff('day', certExpiration, now())\r\n| extend clusterLink = strcat('https://portal.azure.com/#@/resource', id)\r\n| project ClusterName = name, clusterLink, resourceGroup, certExpiration, daysUntilExpiration, location\r\n| order by daysUntilExpiration asc", "size": 0, "title": "Clusters with Certificates Expiring Soon", "noDataMessage": "No clusters with certificates expiring in the next 30 days", "showRefreshButton": true, "showExportToExcel": true, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "gridSettings": { "rowLimit": 2000, "formatters": [ { "columnMatch": "ClusterName", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkColumn": "clusterLink" } }, { "columnMatch": "clusterLink", "formatter": 5 }, { "columnMatch": "certExpiration", "formatter": 6 }, { "columnMatch": "daysUntilExpiration", "formatter": 18, "formatOptions": { "thresholdsOptions": "colors", "thresholdsGrid": [ { "operator": "<=", "thresholdValue": "7", "representation": "redBright", "text": "{0}{1} days" }, { "operator": "<=", "thresholdValue": "14", "representation": "yellow", "text": "{0}{1} days" }, { "operator": "Default", "thresholdValue": null, "representation": "green", "text": "{0}{1} days" } ] } } ], "filter": true, "labelSettings": [ { "columnId": "ClusterName", "label": "AKS Cluster Name" }, { "columnId": "clusterLink" }, { "columnId": "resourceGroup", "label": "Resource Group" }, { "columnId": "certExpiration", "label": "Certificate Expiration" }, { "columnId": "daysUntilExpiration", "label": "Days Until Expiration" }, { "columnId": "location", "label": "Location" } ] } }, "name": "aks-cert-expiration" }, { "type": 1, "content": { "json": "---\r\n## 🧩 AKS Arc Cluster Extensions\r\nArc agent extensions installed on AKS Arc clusters." }, "name": "text-aks-extensions-divider" }, { "type": 9, "content": { "version": "KqlParameterItem/1.0", "parameters": [ { "id": "aks-ext-status-filter-001", "version": "KqlParameterItem/1.0", "name": "AksExtensionStatusFilter", "label": "Extension Status", "type": 2, "isRequired": true, "multiSelect": true, "quote": "'", "delimiter": ",", "typeSettings": { "additionalResourceOptions": [ "value::all" ], "showDefault": false }, "jsonData": "[\"Succeeded\", \"Failed\", \"Creating\", \"Updating\", \"Deleting\"]", "defaultValue": "value::all" }, { "id": "aks-ext-name-filter-001", "version": "KqlParameterItem/1.0", "name": "AksExtensionNameFilter", "label": "Extension Name", "type": 2, "isRequired": true, "multiSelect": true, "quote": "'", "delimiter": ",", "query": "kubernetesconfigurationresources\r\n| where type == \"microsoft.kubernetesconfiguration/extensions\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| extend extensionName = coalesce(tostring(properties.ExtensionType), tostring(properties.extensionType))\r\n| parse tolower(id) with clusterId \"/providers/microsoft.kubernetesconfiguration/extensions/\" *\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.kubernetes/connectedclusters\"\r\n | where properties.infrastructure == \"azure_stack_hci\"\r\n | project clusterId = tolower(id)\r\n) on clusterId\r\n| where isnotempty(extensionName)\r\n| distinct extensionName\r\n| order by extensionName asc", "crossComponentResources": [ "{Subscriptions}" ], "typeSettings": { "additionalResourceOptions": [ "value::all" ], "showDefault": false }, "defaultValue": "value::all", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources" } ], "style": "pills", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources" }, "name": "aks-extensions-filters" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "kubernetesconfigurationresources\r\n| where type == \"microsoft.kubernetesconfiguration/extensions\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| extend status = coalesce(tostring(properties.ProvisioningState), tostring(properties.provisioningState))\r\n| extend extensionName = coalesce(tostring(properties.ExtensionType), tostring(properties.extensionType))\r\n| parse tolower(id) with clusterId \"/providers/microsoft.kubernetesconfiguration/extensions/\" *\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.kubernetes/connectedclusters\"\r\n | where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n | where properties.infrastructure == \"azure_stack_hci\"\r\n | project clusterId = tolower(id), clusterName = name\r\n) on clusterId\r\n| where isnotempty(extensionName)\r\n| where '*' in ({AksExtensionNameFilter}) or extensionName in ({AksExtensionNameFilter})\r\n| where '*' in ({AksExtensionStatusFilter}) or status in ({AksExtensionStatusFilter})\r\n| summarize count() by extensionName, status\r\n| summarize \r\n Succeeded = sumif(count_, status == \"Succeeded\"),\r\n Failed = sumif(count_, status == \"Failed\"),\r\n Creating = sumif(count_, status == \"Creating\"),\r\n Updating = sumif(count_, status == \"Updating\"),\r\n Deleting = sumif(count_, status == \"Deleting\"),\r\n Other = sumif(count_, status !in (\"Succeeded\", \"Failed\", \"Creating\", \"Updating\", \"Deleting\"))\r\n by extensionName\r\n| project extensionName, Succeeded, Failed, Creating, Updating, Deleting, Other\r\n| order by extensionName asc", "size": 0, "showRefreshButton": true, "title": "AKS Extension Status by Type", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "gridSettings": { "rowLimit": 2000, "formatters": [ { "columnMatch": "Succeeded", "formatter": 18, "formatOptions": { "thresholdsOptions": "colors", "thresholdsGrid": [ { "operator": ">", "thresholdValue": "0", "representation": "green", "text": "{0}{1}" }, { "operator": "Default", "thresholdValue": null, "text": "{0}{1}" } ] } }, { "columnMatch": "Failed", "formatter": 18, "formatOptions": { "thresholdsOptions": "colors", "thresholdsGrid": [ { "operator": ">", "thresholdValue": "0", "representation": "redBright", "text": "{0}{1}" }, { "operator": "Default", "thresholdValue": null, "text": "{0}{1}" } ] } }, { "columnMatch": "Creating", "formatter": 18, "formatOptions": { "thresholdsOptions": "colors", "thresholdsGrid": [ { "operator": ">", "thresholdValue": "0", "representation": "blue", "text": "{0}{1}" }, { "operator": "Default", "thresholdValue": null, "text": "{0}{1}" } ] } }, { "columnMatch": "Updating", "formatter": 18, "formatOptions": { "thresholdsOptions": "colors", "thresholdsGrid": [ { "operator": ">", "thresholdValue": "0", "representation": "yellow", "text": "{0}{1}" }, { "operator": "Default", "thresholdValue": null, "text": "{0}{1}" } ] } } ], "labelSettings": [ { "columnId": "extensionName", "label": "Extension" } ] } }, "customWidth": "60", "name": "aks-extension-status-table", "styleSettings": { "margin": "5", "padding": "5", "maxWidth": "60" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "{\"version\":\"Merge/1.0\",\"merges\":[{\"id\":\"aks-ext-merge-001\",\"mergeType\":\"table\",\"leftTable\":\"aks-extension-status-table\"}],\"projectRename\":[{\"originalName\":\"[aks-extension-status-table].extensionName\",\"mergedName\":\"extensionName\",\"fromId\":\"aks-ext-merge-001\"},{\"originalName\":\"[aks-extension-status-table].Succeeded\",\"mergedName\":\"Succeeded\",\"fromId\":\"aks-ext-merge-001\"},{\"originalName\":\"[aks-extension-status-table].Failed\",\"mergedName\":\"Failed\",\"fromId\":\"aks-ext-merge-001\"},{\"originalName\":\"[aks-extension-status-table].Creating\",\"mergedName\":\"Creating\",\"fromId\":\"aks-ext-merge-001\"},{\"originalName\":\"[aks-extension-status-table].Updating\",\"mergedName\":\"Updating\",\"fromId\":\"aks-ext-merge-001\"},{\"originalName\":\"[aks-extension-status-table].Deleting\",\"mergedName\":\"Deleting\",\"fromId\":\"aks-ext-merge-001\"},{\"originalName\":\"[aks-extension-status-table].Other\",\"mergedName\":\"Other\",\"fromId\":\"aks-ext-merge-001\"}]}", "size": 0, "title": "AKS Extension Status Chart", "queryType": 7, "visualization": "categoricalbar", "chartSettings": { "xSettings": { "label": "Extension" }, "ySettings": { "label": "Count" } } }, "customWidth": "40", "showPin": false, "name": "aks-extension-status-chart", "styleSettings": { "margin": "5px", "padding": "5px", "maxWidth": "40" } }, { "type": 1, "content": { "json": "πŸ“š **Knowledge:** [Troubleshoot extension issues for AKS Arc Kubernetes clusters](https://learn.microsoft.com/azure/azure-arc/kubernetes/extensions-troubleshooting)" }, "name": "aks-extensions-knowledge-link" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "kubernetesconfigurationresources\r\n| where type == \"microsoft.kubernetesconfiguration/extensions\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| extend status = coalesce(tostring(properties.ProvisioningState), tostring(properties.provisioningState))\r\n| where status == \"Failed\"\r\n| extend extensionName = coalesce(tostring(properties.ExtensionType), tostring(properties.extensionType))\r\n| extend errorCode = coalesce(tostring(properties.Statuses[0].Code), tostring(properties.Statuses[0].code), tostring(properties.statuses[0].Code), tostring(properties.statuses[0].code))\r\n| extend errorMessage = coalesce(tostring(properties.Statuses[0].Message), tostring(properties.Statuses[0].message), tostring(properties.statuses[0].Message), tostring(properties.statuses[0].message))\r\n| extend errorDetails = iff(isnotempty(errorCode), strcat(errorCode, ': ', errorMessage), errorMessage)\r\n| extend errorDetailsFull = errorDetails\r\n| parse tolower(id) with clusterId \"/providers/microsoft.kubernetesconfiguration/extensions/\" *\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.kubernetes/connectedclusters\"\r\n | where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n | where properties.infrastructure == \"azure_stack_hci\"\r\n | project clusterId = tolower(id), clusterName = name, clusterResourceGroup = resourceGroup, subscriptionId\r\n) on clusterId\r\n| join kind=leftouter (\r\n resourcecontainers\r\n | where type == \"microsoft.resources/subscriptions\"\r\n | project subscriptionId, subscriptionName = name\r\n) on subscriptionId\r\n| extend clusterLink = strcat('https://portal.azure.com/#@/resource', clusterId)\r\n| extend subscriptionLink = strcat('https://portal.azure.com/#@/resource/subscriptions/', subscriptionId)\r\n| project clusterName, clusterLink, extensionName, status, errorDetails, errorDetailsFull, resourceGroup = clusterResourceGroup, subscriptionName, subscriptionLink\r\n| order by clusterName asc, extensionName asc", "size": 0, "showAnalytics": true, "title": "⚠️ Failed AKS Extensions", "noDataMessage": "No failed AKS extensions found", "showRefreshButton": true, "showExportToExcel": true, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "gridSettings": { "rowLimit": 2000, "formatters": [ { "columnMatch": "clusterName", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkColumn": "clusterLink" } }, { "columnMatch": "clusterLink", "formatter": 5 }, { "columnMatch": "status", "formatter": 18, "formatOptions": { "thresholdsOptions": "icons", "thresholdsGrid": [ { "operator": "==", "thresholdValue": "Failed", "representation": "4", "text": "{0}{1}" }, { "operator": "Default", "thresholdValue": null, "text": "{0}{1}" } ] } }, { "columnMatch": "errorDetails", "formatter": 1, "formatOptions": { "linkTarget": "CellDetails", "linkIsContextBlade": true, "customColumnWidthSetting": "45ch" }, "tooltipFormat": { "tooltip": "Click to view full error details" } }, { "columnMatch": "errorDetailsFull", "formatter": 5 }, { "columnMatch": "subscriptionName", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkColumn": "subscriptionLink" } }, { "columnMatch": "subscriptionLink", "formatter": 5 } ], "filter": true, "labelSettings": [ { "columnId": "clusterName", "label": "AKS Cluster Name" }, { "columnId": "clusterLink" }, { "columnId": "extensionName", "label": "Extension" }, { "columnId": "status", "label": "Status" }, { "columnId": "errorDetails", "label": "Error Details" }, { "columnId": "errorDetailsFull" }, { "columnId": "resourceGroup", "label": "Resource Group" }, { "columnId": "subscriptionName", "label": "Subscription Name" }, { "columnId": "subscriptionLink" } ], "rowDetails": { "type": 1, "title": "Error Details", "content": { "json": "### ⚠️ AKS Extension Error Details\r\n\r\n| Property | Value |\r\n|:--|:--|\r\n| **AKS Cluster** | {clusterName} |\r\n| **Extension** | {extensionName} |\r\n| **Status** | {status} |\r\n| **Resource Group** | {resourceGroup} |\r\n\r\n---\r\n\r\n#### Full Error Message\r\n\r\n
\r\n\r\n{errorDetailsFull}\r\n\r\n
\r\n\r\n---\r\n\r\nπŸ’‘ **Tip:** Click on the AKS Cluster Name link to view the extension in the Azure Portal for additional details and remediation options." }, "conditionalVisibility": { "parameterName": "errorDetails", "comparison": "isNotEqualTo", "value": "" } } } }, "name": "aks-failed-extensions-table" }, { "type": 1, "content": { "json": "---\r\n## πŸ”„ AKS Arc GitOps / Flux Status\r\n\r\nMonitor Flux configurations and compliance status across your AKS Arc clusters.\r\n\r\n> πŸ’‘ **Tip:** GitOps views support filtering by **Subscription** and **Resource Group** only. The **Parent Cluster Tag** filter is not supported due to Azure Resource Graph query limitations." }, "name": "text-gitops-header" }, { "type": 11, "content": { "version": "LinkItem/1.0", "style": "nav", "links": [ { "id": "gitops-docs-link", "cellValue": "https://learn.microsoft.com/azure/azure-arc/kubernetes/conceptual-gitops-flux2", "linkTarget": "Url", "linkLabel": "πŸ“š Documentation: GitOps with Flux v2", "style": "link" } ] }, "name": "gitops-docs-link" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "kubernetesconfigurationresources\r\n| where type == \"microsoft.kubernetesconfiguration/fluxconfigurations\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| extend aksId = tolower(substring(id, 0, indexof(id, \"/providers/Microsoft.KubernetesConfiguration\")))\r\n| extend complianceState = iff(isnull(properties.complianceState), \"Pending\", tostring(properties.complianceState))\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.kubernetes/connectedclusters\"\r\n | where properties.infrastructure == \"azure_stack_hci\"\r\n | project aksId = tolower(id)\r\n) on aksId\r\n| summarize Count = count() by complianceState", "size": 0, "title": "Flux Compliance Status", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "piechart", "chartSettings": { "showLegend": true, "legendPosition": "bottom", "seriesLabelSettings": [] } }, "customWidth": "50", "name": "flux-compliance-chart", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "kubernetesconfigurationresources\r\n| where type == \"microsoft.kubernetesconfiguration/fluxconfigurations\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| extend aksId = tolower(substring(id, 0, indexof(id, \"/providers/Microsoft.KubernetesConfiguration\")))\r\n| extend complianceState = iff(isnull(properties.complianceState), \"Pending\", tostring(properties.complianceState))\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.kubernetes/connectedclusters\"\r\n | where properties.infrastructure == \"azure_stack_hci\"\r\n | project aksId = tolower(id)\r\n) on aksId\r\n| summarize Total = count(), Compliant = countif(complianceState == \"Compliant\"), NonCompliant = countif(complianceState == \"Non-Compliant\"), Pending = countif(complianceState == \"Pending\"), Suspended = countif(complianceState == \"Suspended\")\r\n| extend ComplianceRate = iff(Total > 0, round(100.0 * Compliant / Total, 1), 0.0)\r\n| project Total, Compliant, NonCompliant, Pending, Suspended, ComplianceRate", "size": 4, "title": "Flux Configuration Summary", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "tiles", "tileSettings": { "showBorder": true, "titleContent": { "columnMatch": "Total", "formatter": 12, "formatOptions": { "palette": "blue" } }, "leftContent": { "columnMatch": "ComplianceRate", "formatter": 12, "formatOptions": { "palette": "auto" }, "numberFormat": { "unit": 1, "options": { "style": "decimal" } } } } }, "customWidth": "50", "name": "flux-summary-tiles", "styleSettings": { "margin": "5px", "padding": "5px" } }, { "type": 1, "content": { "json": "### πŸ“‹ All Flux Configurations" }, "name": "text-all-flux-header" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "kubernetesconfigurationresources\r\n| where type == \"microsoft.kubernetesconfiguration/fluxconfigurations\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| parse id with * \"ManagedClusters/\" aksClusterName \"/\" *\r\n| parse id with * \"ConnectedClusters/\" arcClusterName \"/\" *\r\n| extend clusterName = iff(isempty(aksClusterName), arcClusterName, aksClusterName)\r\n| extend aksId = tolower(substring(id, 0, indexof(id, \"/providers/Microsoft.KubernetesConfiguration\")))\r\n| extend clusterLink = strcat(\"https://portal.azure.com/#@/resource\", aksId)\r\n| extend configName = name\r\n| extend complianceState = iff(isnull(properties.complianceState), \"Pending\", tostring(properties.complianceState))\r\n| extend sourceKind = tostring(properties.sourceKind)\r\n| extend url = iff(sourceKind =~ \"GitRepository\", tostring(properties.gitRepository.url), iff(sourceKind =~ \"AzureBlob\", tostring(properties.azureBlob.url), tostring(properties.bucket.url)))\r\n| extend branch = tostring(properties.gitRepository.repositoryRef.branch)\r\n| extend namespace = tostring(properties.configNamespace)\r\n| extend lastSourceSyncedAt = todatetime(properties.statuses[0].lastTransitionTime)\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.kubernetes/connectedclusters\"\r\n | where properties.infrastructure == \"azure_stack_hci\"\r\n | project aksId = tolower(id)\r\n) on aksId\r\n| project clusterName, clusterLink, configName, complianceState, sourceKind, url, branch, namespace, lastSourceSyncedAt, resourceGroup, subscriptionId\r\n| order by complianceState asc, clusterName asc", "size": 0, "showAnalytics": true, "title": "All Flux Configurations", "showRefreshButton": true, "showExportToExcel": true, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "gridSettings": { "rowLimit": 2000, "formatters": [ { "columnMatch": "clusterName", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkColumn": "clusterLink" } }, { "columnMatch": "clusterLink", "formatter": 5 }, { "columnMatch": "complianceState", "formatter": 18, "formatOptions": { "thresholdsOptions": "icons", "thresholdsGrid": [ { "operator": "==", "thresholdValue": "Compliant", "representation": "success", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "Non-Compliant", "representation": "4", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "Pending", "representation": "pending", "text": "{0}{1}" }, { "operator": "Default", "thresholdValue": null, "representation": "unknown", "text": "{0}{1}" } ] } }, { "columnMatch": "lastSourceSyncedAt", "formatter": 6 } ], "labelSettings": [ { "columnId": "clusterName", "label": "AKS Cluster Name" }, { "columnId": "configName", "label": "Configuration Name" }, { "columnId": "complianceState", "label": "Compliance State" }, { "columnId": "sourceKind", "label": "Source Kind" }, { "columnId": "url", "label": "Source URL" }, { "columnId": "branch", "label": "Branch" }, { "columnId": "namespace", "label": "Namespace" }, { "columnId": "lastSourceSyncedAt", "label": "Last Synced" }, { "columnId": "resourceGroup", "label": "Resource Group" }, { "columnId": "subscriptionId", "label": "Subscription ID" } ] } }, "name": "flux-all-configurations-table" }, { "type": 1, "content": { "json": "### ⚠️ Non-Compliant Flux Configurations" }, "name": "text-noncompliant-header" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "kubernetesconfigurationresources\r\n| where type == \"microsoft.kubernetesconfiguration/fluxconfigurations\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| extend complianceState = iff(isnull(properties.complianceState), \"Pending\", tostring(properties.complianceState))\r\n| where complianceState == \"Non-Compliant\" or complianceState == \"Pending\"\r\n| parse id with * \"ManagedClusters/\" aksClusterName \"/\" *\r\n| parse id with * \"ConnectedClusters/\" arcClusterName \"/\" *\r\n| extend clusterName = iff(isempty(aksClusterName), arcClusterName, aksClusterName)\r\n| extend aksId = tolower(substring(id, 0, indexof(id, \"/providers/Microsoft.KubernetesConfiguration\")))\r\n| extend clusterLink = strcat(\"https://portal.azure.com/#@/resource\", aksId)\r\n| extend configName = name\r\n| extend sourceKind = tostring(properties.sourceKind)\r\n| extend url = iff(sourceKind =~ \"GitRepository\", tostring(properties.gitRepository.url), iff(sourceKind =~ \"AzureBlob\", tostring(properties.azureBlob.url), tostring(properties.bucket.url)))\r\n| extend errorMessage = tostring(properties.statuses[0].message)\r\n| extend lastSourceSyncedAt = todatetime(properties.statuses[0].lastTransitionTime)\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.kubernetes/connectedclusters\"\r\n | where properties.infrastructure == \"azure_stack_hci\"\r\n | project aksId = tolower(id)\r\n) on aksId\r\n| project clusterName, clusterLink, configName, complianceState, sourceKind, url, errorMessage, lastSourceSyncedAt, resourceGroup\r\n| order by clusterName asc", "size": 0, "showAnalytics": true, "title": "Non-Compliant Flux Configurations", "noDataMessage": "βœ… All Flux configurations are compliant", "showRefreshButton": true, "showExportToExcel": true, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "gridSettings": { "rowLimit": 2000, "formatters": [ { "columnMatch": "clusterName", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkColumn": "clusterLink" } }, { "columnMatch": "clusterLink", "formatter": 5 }, { "columnMatch": "complianceState", "formatter": 18, "formatOptions": { "thresholdsOptions": "icons", "thresholdsGrid": [ { "operator": "==", "thresholdValue": "Non-Compliant", "representation": "4", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "Pending", "representation": "pending", "text": "{0}{1}" }, { "operator": "Default", "thresholdValue": null, "representation": "warning", "text": "{0}{1}" } ] } }, { "columnMatch": "url", "formatter": 7, "formatOptions": { "linkTarget": "Url" } }, { "columnMatch": "errorMessage", "formatter": 1, "formatOptions": { "linkTarget": "CellDetails", "linkIsContextBlade": true, "customColumnWidthSetting": "45ch" }, "tooltipFormat": { "tooltip": "Click to view full error details" } }, { "columnMatch": "lastSourceSyncedAt", "formatter": 6 } ], "filter": true, "labelSettings": [ { "columnId": "clusterName", "label": "AKS Cluster Name" }, { "columnId": "configName", "label": "Configuration Name" }, { "columnId": "complianceState", "label": "Compliance State" }, { "columnId": "sourceKind", "label": "Source Kind" }, { "columnId": "url", "label": "Source URL" }, { "columnId": "errorMessage", "label": "Error Details" }, { "columnId": "lastSourceSyncedAt", "label": "Last Synced" }, { "columnId": "resourceGroup", "label": "Resource Group" } ] } }, "name": "flux-noncompliant-table" } ] }, "conditionalVisibility": { "parameterName": "selectedTab", "comparison": "isEqualTo", "value": "4" }, "name": "aks-arc-clusters" }, { "type": 12, "content": { "version": "NotebookGroup/1.0", "groupType": "editable", "title": "System Health", "items": [ { "type": 1, "content": { "json": "## πŸ“‹ Azure Local System Health\r\n\r\n**Last Refreshed:** {TimeRange:label} | Click refresh on any tile to update data" }, "name": "text - 0" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "extensibilityresources\r\n| where type == \"microsoft.azurestackhci/clusters/updatesummaries\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| extend clusterName = tolower(tostring(split(id, '/')[8]))\r\n| extend clusterRG = tolower(resourceGroup)\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterName = tolower(name), clusterRG = tolower(resourceGroup)\r\n) on clusterName, clusterRG\r\n| extend healthState = tostring(properties.healthState)\r\n| summarize Count = count() by healthState\r\n| order by Count desc", "size": 0, "title": "Health State Distribution", "showAnalytics": true, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "piechart", "chartSettings": { "showLegend": true, "legendPosition": "bottom", "seriesLabelSettings": [] } }, "customWidth": "50", "name": "query - 2" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "extensibilityresources\r\n| where type == \"microsoft.azurestackhci/clusters/updatesummaries\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| extend clusterName = tolower(tostring(split(id, '/')[8]))\r\n| extend clusterRG = tolower(resourceGroup)\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterName = tolower(name), clusterRG = tolower(resourceGroup)\r\n) on clusterName, clusterRG\r\n| extend currentVersion = tostring(properties.currentVersion)\r\n| summarize ClusterCount = count() by currentVersion\r\n| order by ClusterCount desc", "size": 0, "title": "Version Distribution", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "piechart", "chartSettings": { "showLegend": true, "legendPosition": "bottom" } }, "customWidth": "50", "name": "version-distribution-chart" }, { "type": 1, "content": { "json": "
\r\n⚠️ Update Readiness Summary by Health State
\r\nThis table shows the readiness status of all Azure Local clusters. Review clusters with Warning or Critical health status using the tables below, to remove blockers that prevent applying updates.\r\n
" }, "name": "update-readiness-table-header" }, { "type": 1, "content": { "json": "πŸ“š **Knowledge:** [Understand Azure Local update phases](https://learn.microsoft.com/azure/azure-local/update/update-phases-23h2)" }, "name": "text - update phases knowledge link" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "extensibilityresources\r\n| where type == \"microsoft.azurestackhci/clusters/updatesummaries\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| extend clusterName = tolower(tostring(split(id, '/')[8]))\r\n| extend clusterRG = tolower(resourceGroup)\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterName = tolower(name), clusterRG = tolower(resourceGroup)\r\n) on clusterName, clusterRG\r\n| extend hciClusterName = tostring(split(id, '/')[8])\r\n| extend healthState = tostring(properties.healthState)\r\n| extend state = tostring(properties.state)\r\n| summarize \r\n Success = countif(healthState == \"Success\"),\r\n Warning = countif(healthState == \"Warning\"),\r\n Failure = countif(healthState == \"Failure\"),\r\n InProgress = countif(healthState == \"InProgress\"),\r\n Unknown = countif(healthState == \"Unknown\")\r\n by state\r\n| extend Total = Success + Warning + Failure + InProgress + Unknown\r\n| project state, Total, Success, Warning, Failure, InProgress, Unknown\r\n| order by Total desc", "size": 0, "title": "Update Readiness Summary", "showAnalytics": true, "showRefreshButton": true, "showExportToExcel": true, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "gridSettings": { "rowLimit": 2000, "formatters": [ { "columnMatch": "state", "formatter": 18, "formatOptions": { "thresholdsOptions": "icons", "thresholdsGrid": [ { "operator": "==", "thresholdValue": "AppliedSuccessfully", "representation": "Blank", "text": "βœ… Applied successfully" }, { "operator": "==", "thresholdValue": "UpToDate", "representation": "Blank", "text": "βœ… Up to date" }, { "operator": "==", "thresholdValue": "UpdateFailed", "representation": "Blank", "text": "❌ Update failed" }, { "operator": "==", "thresholdValue": "PreparationFailed", "representation": "Blank", "text": "❌ Preparation failed" }, { "operator": "==", "thresholdValue": "NeedsAttention", "representation": "Blank", "text": "⚠️ Needs attention" }, { "operator": "==", "thresholdValue": "UpdateAvailable", "representation": "Blank", "text": "πŸ”„ Updates available" }, { "operator": "==", "thresholdValue": "UpdateInProgress", "representation": "Blank", "text": "⏳ Update in progress" }, { "operator": "==", "thresholdValue": "PreparationInProgress", "representation": "Blank", "text": "⏳ Preparation in progress" }, { "operator": "Default", "thresholdValue": null, "representation": "Blank", "text": "{0}{1}" } ] } }, { "columnMatch": "Success", "formatter": 0 }, { "columnMatch": "Warning", "formatter": 0 }, { "columnMatch": "Failure", "formatter": 0 } ], "labelSettings": [ { "columnId": "state", "label": "Update Status" }, { "columnId": "Total", "label": "Total" }, { "columnId": "Success", "label": "Health State: Success" }, { "columnId": "Warning", "label": "Health State: Warning" }, { "columnId": "Failure", "label": "Health State: Critical", "comment": "Updates are Blocked" }, { "columnId": "InProgress", "label": "Update In Progress" }, { "columnId": "Unknown", "label": "Unknown" } ] } }, "name": "query - 1" }, { "type": 1, "content": { "json": "## ❌ Health Check Failures By Reason Summary" }, "name": "text - 7" }, { "type": 1, "content": { "json": "πŸ“š **Knowledge:** [Troubleshoot Azure Local Readiness Checks using Azure portal or PowerShell](https://learn.microsoft.com/azure/azure-local/update/update-troubleshooting-23h2?view=azloc-2601#troubleshoot-readiness-checks)" }, "name": "text - readiness knowledge link" }, { "type": 1, "content": { "json": "This table shows system health failures, sorted by 'Cluster Count', to show you the highest frequency issues at the top of the table, along with the list of 'Affected Clusters'." }, "name": "text - health failures description" }, { "type": 9, "content": { "version": "KqlParameterItem/1.0", "parameters": [ { "id": "f8a2c3d4-e5f6-7890-abcd-ef1234567890", "version": "KqlParameterItem/1.0", "name": "FailureClusterFilter", "label": "Filter by Cluster", "type": 2, "isRequired": true, "multiSelect": true, "quote": "'", "delimiter": ",", "query": "extensibilityresources\r\n| where type == \"microsoft.azurestackhci/clusters/updatesummaries\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| extend clusterName = tolower(tostring(split(id, '/')[8]))\r\n| extend clusterRG = tolower(resourceGroup)\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterName = tolower(name), clusterRG = tolower(resourceGroup)\r\n) on clusterName, clusterRG\r\n| extend hciClusterName = tostring(split(id, '/')[8])\r\n| extend healthCheckResult = properties.healthCheckResult\r\n| mv-expand healthCheck = healthCheckResult\r\n| where tostring(healthCheck.status) == \"Failed\"\r\n| where todatetime(healthCheck.timestamp) >= {TimeRange:start}\r\n| distinct hciClusterName\r\n| order by hciClusterName asc", "crossComponentResources": [ "{Subscriptions}" ], "typeSettings": { "additionalResourceOptions": [ "value::all" ], "showDefault": false }, "defaultValue": "value::all", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources" }, { "id": "severity-filter-001", "version": "KqlParameterItem/1.0", "name": "SeverityFilter", "label": "Filter by Severity", "type": 2, "isRequired": true, "multiSelect": true, "quote": "'", "delimiter": ",", "typeSettings": { "additionalResourceOptions": [], "showDefault": false }, "jsonData": "[\r\n { \"value\": \"Critical\", \"label\": \"Critical\" },\r\n { \"value\": \"Warning\", \"label\": \"Warning\" },\r\n { \"value\": \"Informational\", \"label\": \"Informational\" }\r\n]", "defaultValue": [ "Critical", "Warning" ], "value": [ "Critical", "Warning" ] } ], "style": "pills", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources" }, "name": "failure-cluster-filter" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "extensibilityresources\r\n| where type == \"microsoft.azurestackhci/clusters/updatesummaries\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| extend clusterName = tolower(tostring(split(id, '/')[8]))\r\n| extend clusterRG = tolower(resourceGroup)\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterName = tolower(name), clusterRG = tolower(resourceGroup)\r\n) on clusterName, clusterRG\r\n| extend hciClusterName = tostring(split(id, '/')[8])\r\n| extend healthCheckResult = properties.healthCheckResult\r\n| mv-expand healthCheck = healthCheckResult\r\n| where tostring(healthCheck.status) == \"Failed\"\r\n| where todatetime(healthCheck.timestamp) >= {TimeRange:start}\r\n| extend displayName = tostring(split(tostring(healthCheck.name), '/')[0])\r\n| extend severity = tostring(healthCheck.severity)\r\n| where '*' in ({FailureClusterFilter}) or hciClusterName in ({FailureClusterFilter})\r\n| where severity in ({SeverityFilter})\r\n| summarize FailureCount = count(), AffectedClustersList = make_set(hciClusterName), LatestOccurrence = max(todatetime(healthCheck.timestamp)) by displayName, severity\r\n| extend AffectedClusters = strcat_array(AffectedClustersList, ', '), ClusterCount = array_length(AffectedClustersList)\r\n| extend severityOrder = case(\r\n severity == \"Critical\", 1,\r\n severity == \"Warning\", 2,\r\n severity == \"Informational\", 3,\r\n 4)\r\n| project displayName, severity, FailureCount, ClusterCount, AffectedClusters, LatestOccurrence, severityOrder\r\n| order by ClusterCount desc, severityOrder asc, FailureCount desc\r\n", "size": 0, "showAnalytics": true, "showRefreshButton": true, "showExportToExcel": true, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "gridSettings": { "rowLimit": 2000, "formatters": [ { "columnMatch": "severity", "formatter": 18, "formatOptions": { "thresholdsOptions": "icons", "thresholdsGrid": [ { "operator": "==", "thresholdValue": "Critical", "representation": "4", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "Warning", "representation": "2", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "Informational", "representation": "info", "text": "{0}{1}" }, { "operator": "Default", "thresholdValue": null, "representation": "unknown", "text": "{0}{1}" } ] } }, { "columnMatch": "severityOrder", "formatter": 5 }, { "columnMatch": "AffectedClustersList", "formatter": 5 } ], "labelSettings": [ { "columnId": "displayName", "label": "Failure Reason" }, { "columnId": "severity", "label": "Failure Severity" }, { "columnId": "AffectedClusters", "label": "Affected Clusters" }, { "columnId": "LatestOccurrence", "label": "Latest Occurrence" }, { "columnId": "ClusterCount", "label": "Cluster Count" } ] } }, "name": "query - 6" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "extensibilityresources\r\n| where type == \"microsoft.azurestackhci/clusters/updatesummaries\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| extend clusterName = tolower(tostring(split(id, '/')[8]))\r\n| extend clusterRG = tolower(resourceGroup)\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterName = tolower(name), clusterRG = tolower(resourceGroup)\r\n) on clusterName, clusterRG\r\n| extend hciClusterName = tostring(split(id, '/')[8])\r\n| extend healthCheckResult = properties.healthCheckResult\r\n| mv-expand healthCheck = healthCheckResult\r\n| where tostring(healthCheck.status) == \"Failed\"\r\n| where todatetime(healthCheck.timestamp) >= {TimeRange:start}\r\n| extend displayName = tostring(split(tostring(healthCheck.name), '/')[0])\r\n| extend severity = tostring(healthCheck.severity)\r\n| where '*' in ({FailureClusterFilter}) or hciClusterName in ({FailureClusterFilter})\r\n| where severity in ({SeverityFilter})\r\n| summarize ClusterCount = dcount(hciClusterName) by displayName\r\n| top 5 by ClusterCount desc\r\n| order by ClusterCount desc", "size": 0, "title": "Top 5 - System Health Check Issues", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "piechart", "chartSettings": { "showLegend": true, "legendPosition": "bottom" } }, "customWidth": "50", "name": "top5-health-check-issues-pie" }, { "type": 1, "content": { "json": "## πŸ“‹ System Health Check Filters" }, "name": "text - 5" }, { "type": 9, "content": { "version": "KqlParameterItem/1.0", "parameters": [ { "id": "b902e267-cb73-433e-a5f4-5627f38003e5", "version": "KqlParameterItem/1.0", "name": "clustername", "type": 2, "multiSelect": true, "quote": "'", "delimiter": ",", "query": "resources\r\n| where type == \"microsoft.azurestackhci/clusters\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n| project name", "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "typeSettings": { "additionalResourceOptions": [ "value::all" ], "showDefault": false }, "timeContext": { "durationMs": 86400000 }, "defaultValue": "value::all", "queryType": 1, "value": [ "value::all" ], "label": "Cluster Name" }, { "id": "4f902e36-2dbc-4bdf-89aa-44d2bb919cc6", "version": "KqlParameterItem/1.0", "name": "healthState", "label": "Health State", "type": 2, "multiSelect": true, "quote": "'", "delimiter": ",", "typeSettings": { "additionalResourceOptions": [ "value::all" ], "showDefault": false }, "jsonData": "[\"Success\",\r\n\"Failure\",\r\n\"Warning\",\r\n\"InProgress\"\r\n]", "timeContext": { "durationMs": 86400000 }, "defaultValue": "value::all", "value": [ "value::all" ] } ], "style": "pills", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces" }, "name": "param - cluster" }, { "type": 1, "content": { "json": "πŸ’‘ **Tip:** The 'Age of Health Results' column shows how many days ago the cluster last reported its health check results to Azure. A high value (highlighted in yellow/red) may indicate connectivity issues or that the cluster hasn't performed health checks recently. To troubleshoot: verify the cluster is connected to Azure, check the Azure Arc agent status, and run `Test-EnvironmentReadiness` on the cluster.", "style": "info" }, "name": "health-age-tip" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.azurestackhci/clusters\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n| extend azureConnection = case(\r\n properties.status =~ 'NotSpecified', 'Unknown',\r\n properties.status =~ 'ConnectedRecently', 'Connected',\r\n properties.status =~ 'NotConnectedRecently', 'Not connected recently',\r\n properties.status =~ 'NotYetRegistered', 'Not yet registered',\r\n properties.status =~ 'Error', 'Error',\r\n properties.status =~ 'Disconnected', 'Disconnected',\r\n 'Unknown'\r\n)\r\n| project clusterName = name, clusterId = id, resourceGroup, subscriptionId, azureConnection\r\n| join kind=inner (\r\n extensibilityresources\r\n | where type == \"microsoft.azurestackhci/clusters/updatesummaries\"\r\n | where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n | extend hciClusterName = tostring(split(id, '/')[8])\r\n | extend healthState = tostring(properties.healthState)\r\n | extend healthStateDisplay = case(\r\n healthState =~ 'Success', 'Healthy',\r\n healthState =~ 'Failure', 'Critical',\r\n healthState =~ 'Warning', 'Warning',\r\n healthState =~ 'InProgress', 'In progress',\r\n healthState =~ 'Error', 'Health check failed',\r\n healthState =~ 'Unknown', 'Unknown',\r\n healthState\r\n )\r\n | extend state = tostring(properties.state)\r\n | extend lastChecked = todatetime(properties.lastChecked)\r\n | extend currentVersion = tostring(properties.currentVersion)\r\n | extend packageVersions = properties.packageVersions\r\n | project hciClusterName, healthState, healthStateDisplay, state, lastChecked, currentVersion, packageVersions\r\n) on $left.clusterName == $right.hciClusterName\r\n| mv-expand pkg = packageVersions\r\n| extend pkgType = tostring(pkg.packageType)\r\n| extend pkgVersion = tostring(pkg.version)\r\n| summarize sbeVersion = maxif(pkgVersion, pkgType =~ 'SBE') by clusterName, clusterId, resourceGroup, subscriptionId, azureConnection, healthState, healthStateDisplay, state, lastChecked, currentVersion\r\n| join kind=leftouter (\r\n resources\r\n | where type == \"microsoft.hybridcompute/machines\"\r\n | where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n | where properties.cloudMetadata.provider == \"AzSHCI\"\r\n| where kind != \"HCI\"\r\n | summarize nodeCount = count() by resourceGroup\r\n) on resourceGroup\r\n| where clusterName in ({clustername})\r\n| where healthState in ({healthState})\r\n| extend HealthResultsAge = datetime_diff('day', now(), lastChecked)\r\n| extend clusterLink = strcat('https://portal.azure.com/#@/resource', clusterId)\r\n| project clusterName, clusterLink, HealthStatus = healthStateDisplay, healthState, state, currentVersion, azureConnection, sbeVersion = coalesce(sbeVersion, ''), lastChecked, HealthResultsAge, resourceGroup, nodeCount = coalesce(nodeCount, 0)\r\n| order by HealthResultsAge desc, clusterName asc", "size": 0, "showAnalytics": true, "title": "System Health Checks Overview", "showRefreshButton": true, "showExportToExcel": true, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "gridSettings": { "rowLimit": 2000, "formatters": [ { "columnMatch": "clusterName", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkColumn": "clusterLink" } }, { "columnMatch": "clusterLink", "formatter": 5 }, { "columnMatch": "HealthStatus", "formatter": 18, "formatOptions": { "thresholdsOptions": "icons", "thresholdsGrid": [ { "operator": "==", "thresholdValue": "Healthy", "representation": "success", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "Critical", "representation": "4", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "Warning", "representation": "2", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "In progress", "representation": "pending", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "Health check failed", "representation": "4", "text": "{0}{1}" }, { "operator": "Default", "thresholdValue": null, "representation": "unknown", "text": "{0}{1}" } ] } }, { "columnMatch": "healthState", "formatter": 5 }, { "columnMatch": "state", "formatter": 18, "formatOptions": { "thresholdsOptions": "icons", "thresholdsGrid": [ { "operator": "==", "thresholdValue": "UpdateAvailable", "representation": "Blank", "text": "πŸ”„ Updates available" }, { "operator": "==", "thresholdValue": "NeedsAttention", "representation": "Blank", "text": "⚠️ Needs attention" }, { "operator": "==", "thresholdValue": "UpToDate", "representation": "Blank", "text": "βœ… Up to date" }, { "operator": "==", "thresholdValue": "AppliedSuccessfully", "representation": "Blank", "text": "βœ… Applied successfully" }, { "operator": "==", "thresholdValue": "UpdateInProgress", "representation": "Blank", "text": "⏳ Update in progress" }, { "operator": "==", "thresholdValue": "UpdateFailed", "representation": "Blank", "text": "❌ Update failed" }, { "operator": "==", "thresholdValue": "PreparationInProgress", "representation": "Blank", "text": "⏳ Preparation in progress" }, { "operator": "==", "thresholdValue": "PreparationFailed", "representation": "Blank", "text": "❌ Preparation failed" }, { "operator": "Default", "thresholdValue": null, "representation": "Blank", "text": "{0}{1}" } ] } }, { "columnMatch": "HealthResultsAge", "formatter": 18, "formatOptions": { "thresholdsOptions": "colors", "thresholdsGrid": [ { "operator": ">=", "thresholdValue": "4", "representation": "redBright", "text": "{0}{1} days" }, { "operator": ">=", "thresholdValue": "2", "representation": "yellow", "text": "{0}{1} days" }, { "operator": "Default", "thresholdValue": null, "text": "{0}{1} days" } ] } } ], "filter": true, "labelSettings": [ { "columnId": "clusterName", "label": "Cluster Name" }, { "columnId": "clusterLink" }, { "columnId": "HealthStatus", "label": "Health Status" }, { "columnId": "state", "label": "Update Status" }, { "columnId": "currentVersion", "label": "Current Version" }, { "columnId": "azureConnection", "label": "Azure Connection" }, { "columnId": "sbeVersion", "label": "SBE Version" }, { "columnId": "lastChecked", "label": "Last Checked" }, { "columnId": "HealthResultsAge", "label": "Age of Health Results" }, { "columnId": "resourceGroup", "label": "Resource Group" }, { "columnId": "nodeCount", "label": "Node Count" } ] } }, "name": "query - 3" }, { "type": 1, "content": { "json": "πŸ’‘ **Tip:** Review the 'πŸ“¦ Clusters with Updates Available' table in the 'πŸ”„ Update Progress' tab for more information on available updates, including SBE (Solution Builder Extension) dependency details.", "style": "info" }, "name": "health-overview-tip" }, { "type": 1, "content": { "json": "## πŸ” Detailed Health Check Results" }, "name": "detailed-health-check-results-header" }, { "type": 1, "content": { "json": "πŸ“š **Documentation:** [Troubleshooting Azure Local Updates](https://learn.microsoft.com/azure/azure-local/update/update-troubleshooting-23h2#using-powershell)\r\n\r\nπŸ“š **Knowledge:** [Review TroubleShooting Guides (TSGs) in the Azure Local GitHub Supportability Forum, (search for the error or scenario)](https://github.com/Azure/AzureLocal-Supportability)" }, "name": "troubleshooting-link" }, { "type": 9, "content": { "version": "KqlParameterItem/1.0", "parameters": [ { "id": "detail-cluster-001", "version": "KqlParameterItem/1.0", "name": "detailClustername", "type": 2, "multiSelect": true, "quote": "'", "delimiter": ",", "query": "resources\r\n| where type == \"microsoft.azurestackhci/clusters\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n| project name", "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "typeSettings": { "additionalResourceOptions": [ "value::all" ], "showDefault": false }, "timeContext": { "durationMs": 86400000 }, "defaultValue": "value::all", "queryType": 1, "value": [ "value::all" ], "label": "Cluster Name" }, { "id": "detail-healthstate-001", "version": "KqlParameterItem/1.0", "name": "detailHealthState", "label": "Health Check State", "type": 2, "multiSelect": true, "quote": "'", "delimiter": ",", "typeSettings": { "additionalResourceOptions": [ "value::all" ], "showDefault": false }, "jsonData": "[\"Success\",\r\n\"Failure\",\r\n\"Warning\",\r\n\"InProgress\"\r\n]", "timeContext": { "durationMs": 86400000 }, "defaultValue": "value::all", "value": [ "Failure", "Warning" ] }, { "id": "detail-stepstatus-001", "version": "KqlParameterItem/1.0", "name": "detailStepStatus", "label": "Health Check Result", "type": 2, "multiSelect": true, "quote": "'", "delimiter": ",", "typeSettings": { "additionalResourceOptions": [ "value::all" ], "showDefault": false }, "jsonData": "[\"Succeeded\", \"Failed\"]", "timeContext": { "durationMs": 86400000 }, "defaultValue": "value::all", "value": [ "Failed" ] }, { "id": "detail-severity-001", "version": "KqlParameterItem/1.0", "name": "detailSeverity", "label": "Severity", "type": 2, "isRequired": true, "multiSelect": true, "quote": "'", "delimiter": ",", "typeSettings": { "additionalResourceOptions": [ "value::all" ] }, "jsonData": "[\"Critical\", \"Warning\", \"Informational\"]", "defaultValue": "value::all", "value": [ "Critical" ] } ], "style": "pills", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces" }, "name": "param-detail-filters" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "resources\r\n| where type == \"microsoft.azurestackhci/clusters\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n| project clusterName = name, clusterId = id, resourceGroup, subscriptionId\r\n| join kind=inner (\r\n extensibilityresources\r\n | where type == \"microsoft.azurestackhci/clusters/updatesummaries\"\r\n | where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n | extend hciClusterName = tostring(split(id, '/')[8])\r\n | extend lastChecked = todatetime(properties.lastChecked)\r\n | extend healthState = tostring(properties.healthState)\r\n | extend provisioningState = tostring(properties.provisioningState)\r\n | extend healthCheckResult = properties.healthCheckResult\r\n | mv-expand healthCheck = healthCheckResult\r\n | extend severityOrder = case(\r\n tostring(healthCheck.severity) == \"Critical\", 1,\r\n tostring(healthCheck.severity) == \"Warning\", 2,\r\n tostring(healthCheck.severity) == \"Informational\", 3,\r\n 4)\r\n | project hciClusterName, healthState, stepStatus = tostring(healthCheck.status), checkName = tostring(healthCheck.name), displayName = tostring(healthCheck.displayName), description = tostring(healthCheck.description), remediation = tostring(healthCheck.remediation), severity = tostring(healthCheck.severity), targetResourceName = tostring(healthCheck.targetResourceName), targetResourceType = tostring(healthCheck.targetResourceType), timestamp = todatetime(healthCheck.timestamp), lastChecked, severityOrder\r\n) on $left.clusterName == $right.hciClusterName\r\n| where clusterName in ({detailClustername})\r\n| where healthState in ({detailHealthState})\r\n| where stepStatus in ({detailStepStatus})\r\n| where severity in ({detailSeverity})\r\n| extend clusterLink = strcat('https://portal.azure.com/#@/resource', clusterId)\r\n| extend DaysSinceCheck = datetime_diff('day', now(), lastChecked)\r\n| extend remediationLink = iff(remediation startswith 'https://', remediation, '')\r\n| project clusterName, clusterLink, healthState, stepStatus, displayName, description, remediation, remediationLink, severity, targetResourceName, targetResourceType, timestamp, resourceGroup, lastChecked, DaysSinceCheck, severityOrder\r\n| order by severityOrder asc, lastChecked desc", "size": 0, "showAnalytics": true, "showRefreshButton": true, "showExportToExcel": true, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "gridSettings": { "rowLimit": 2000, "formatters": [ { "columnMatch": "clusterName", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkColumn": "clusterLink" } }, { "columnMatch": "clusterLink", "formatter": 5 }, { "columnMatch": "healthState", "formatter": 18, "formatOptions": { "thresholdsOptions": "icons", "thresholdsGrid": [ { "operator": "==", "thresholdValue": "Success", "representation": "success", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "Failure", "representation": "4", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "Warning", "representation": "2", "text": "{0}{1}" }, { "operator": "Default", "thresholdValue": null, "representation": "unknown", "text": "{0}{1}" } ] } }, { "columnMatch": "stepStatus", "formatter": 18, "formatOptions": { "thresholdsOptions": "icons", "thresholdsGrid": [ { "operator": "==", "thresholdValue": "Succeeded", "representation": "success", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "Failed", "representation": "4", "text": "{0}{1}" }, { "operator": "Default", "thresholdValue": null, "representation": "unknown", "text": "{0}{1}" } ] } }, { "columnMatch": "severity", "formatter": 18, "formatOptions": { "thresholdsOptions": "icons", "thresholdsGrid": [ { "operator": "==", "thresholdValue": "Critical", "representation": "4", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "Warning", "representation": "2", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "Informational", "representation": "info", "text": "{0}{1}" }, { "operator": "Default", "thresholdValue": null, "representation": "unknown", "text": "{0}{1}" } ] } }, { "columnMatch": "remediation", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkColumn": "remediationLink" } }, { "columnMatch": "remediationLink", "formatter": 5 }, { "columnMatch": "DaysSinceCheck", "formatter": 18, "formatOptions": { "thresholdsOptions": "colors", "thresholdsGrid": [ { "operator": ">=", "thresholdValue": "4", "representation": "redBright", "text": "{0}{1} days" }, { "operator": ">=", "thresholdValue": "2", "representation": "yellow", "text": "{0}{1} days" }, { "operator": "Default", "thresholdValue": null, "text": "{0}{1} days" } ] } }, { "columnMatch": "severityOrder", "formatter": 5 } ], "filter": true, "labelSettings": [ { "columnId": "clusterName", "label": "Cluster Name" }, { "columnId": "clusterLink" }, { "columnId": "healthState", "label": "Health State" }, { "columnId": "stepStatus", "label": "Check Result" }, { "columnId": "displayName", "label": "Display Name" }, { "columnId": "description", "label": "Description" }, { "columnId": "remediation", "label": "Remediation" }, { "columnId": "remediationLink" }, { "columnId": "severity", "label": "Severity" }, { "columnId": "targetResourceName", "label": "Target Resource Name" }, { "columnId": "targetResourceType", "label": "Target Resource Type" }, { "columnId": "timestamp", "label": "Timestamp" }, { "columnId": "resourceGroup", "label": "Resource Group" }, { "columnId": "lastChecked", "label": "Last Checked" }, { "columnId": "DaysSinceCheck", "label": "Days Since Check" } ] } }, "name": "query - 4" } ] }, "conditionalVisibility": { "parameterName": "selectedTab", "comparison": "isEqualTo", "value": "3" }, "name": "grp-update-readiness" }, { "type": 12, "content": { "version": "NotebookGroup/1.0", "groupType": "editable", "title": "Update Progress", "items": [ { "type": 1, "content": { "json": "## πŸ”„ Update Progress & History\n\nTrack ongoing updates and review historical update runs across your Azure Local clusters.\n\n**Last Refreshed:** {TimeRange:label} | Click refresh on any tile to update data" }, "name": "text-update-progress-header" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "extensibilityresources\r\n| where type == \"microsoft.azurestackhci/clusters/updatesummaries\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| extend clusterName = tolower(tostring(split(id, '/')[8]))\r\n| extend clusterRG = tolower(resourceGroup)\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterName = tolower(name), clusterRG = tolower(resourceGroup)\r\n) on clusterName, clusterRG\r\n| extend hciClusterName = tostring(split(id, '/')[8])\r\n| extend state = tostring(properties.state)\r\n| summarize Count = count() by state\r\n| order by Count desc", "size": 3, "title": "Update State Summary", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "tiles", "tileSettings": { "titleContent": { "columnMatch": "state", "formatter": 1 }, "leftContent": { "columnMatch": "Count", "formatter": 12, "formatOptions": { "palette": "auto" } }, "showBorder": true, "size": "auto" } }, "customWidth": "50", "name": "update-state-tiles" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "extensibilityresources\r\n| where type == \"microsoft.azurestackhci/clusters/updatesummaries\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| extend clusterName = tolower(tostring(split(id, '/')[8]))\r\n| extend clusterRG = tolower(resourceGroup)\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterName = tolower(name), clusterRG = tolower(resourceGroup)\r\n) on clusterName, clusterRG\r\n| extend state = tostring(properties.state)\r\n| summarize Count = count() by state\r\n| order by Count desc", "size": 0, "title": "Update State Distribution", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "piechart", "chartSettings": { "seriesLabelSettings": [] } }, "customWidth": "50", "name": "update-state-pie" }, { "type": 1, "content": { "json": "πŸ’‘ **Tip:** Use the 'Time Period' and 'Solution Update' filters to view Azure Local Update Insights for your environment.", "style": "upsell" }, "name": "update-filters-tip" }, { "type": 9, "content": { "version": "KqlParameterItem/1.0", "parameters": [ { "id": "update-attempts-time-filter", "version": "KqlParameterItem/1.0", "name": "UpdateAttemptsTimeFilter", "label": "Time Period", "type": 2, "isRequired": true, "typeSettings": { "additionalResourceOptions": [], "showDefault": false }, "jsonData": "[{\"value\": \"7\", \"label\": \"1 week\"}, {\"value\": \"14\", \"label\": \"2 weeks\"}, {\"value\": \"30\", \"label\": \"1 month\"}, {\"value\": \"90\", \"label\": \"3 months\"}, {\"value\": \"180\", \"label\": \"6 months\"}, {\"value\": \"270\", \"label\": \"9 months\"}, {\"value\": \"365\", \"label\": \"12 months\"}]", "defaultValue": "90", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources" }, { "id": "solution-update-filter", "version": "KqlParameterItem/1.0", "name": "SolutionUpdateFilter", "label": "Solution Update", "type": 2, "isRequired": true, "multiSelect": true, "quote": "'", "delimiter": ",", "query": "extensibilityresources\r\n| where type == \"microsoft.azurestackhci/clusters/updates/updateruns\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| extend updateName = tostring(split(id, '/updates/')[1])\r\n| extend updateName = tostring(split(updateName, '/updateruns')[0])\r\n| extend solutionUpdate = tostring(split(updateName, '/')[0])\r\n| where isnotempty(solutionUpdate)\r\n| distinct solutionUpdate\r\n| order by solutionUpdate desc", "crossComponentResources": [ "{Subscriptions}" ], "typeSettings": { "additionalResourceOptions": [ "value::all" ], "showDefault": false }, "defaultValue": "value::all", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources" } ], "style": "pills", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources" }, "name": "update-attempts-time-filter" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "extensibilityresources\r\n| where type == \"microsoft.azurestackhci/clusters/updates/updateruns\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| extend state = tostring(properties.state)\r\n| extend timeStarted = todatetime(properties.timeStarted)\r\n| where timeStarted >= ago({UpdateAttemptsTimeFilter}d)\r\n| extend updateName = tostring(split(id, '/updates/')[1])\r\n| extend updateName = tostring(split(updateName, '/updateruns')[0])\r\n| extend solutionUpdate = tostring(split(updateName, '/')[0])\r\n| where '*' in ({SolutionUpdateFilter}) or solutionUpdate in ({SolutionUpdateFilter})\r\n| extend TimeBucket = case(\r\n {UpdateAttemptsTimeFilter} <= 30, startofday(timeStarted),\r\n {UpdateAttemptsTimeFilter} <= 90, startofweek(timeStarted),\r\n startofmonth(timeStarted)\r\n)\r\n| summarize Succeeded = countif(state == 'Succeeded'), Failed = countif(state == 'Failed'), InProgress = countif(state == 'InProgress') by TimeBucket\r\n| union (\r\n resources\r\n | take 1\r\n | mv-expand TimeBucket = range(startofday(ago({UpdateAttemptsTimeFilter}d)), startofday(now()), 1d) to typeof(datetime)\r\n | extend TimeBucket = case(\r\n {UpdateAttemptsTimeFilter} <= 30, startofday(TimeBucket),\r\n {UpdateAttemptsTimeFilter} <= 90, startofweek(TimeBucket),\r\n startofmonth(TimeBucket)\r\n )\r\n | summarize by TimeBucket\r\n | extend Succeeded = 0, Failed = 0, InProgress = 0\r\n)\r\n| summarize Succeeded = sum(Succeeded), Failed = sum(Failed), InProgress = sum(InProgress) by TimeBucket\r\n| order by TimeBucket asc\r\n| extend TimeLabel = case(\r\n {UpdateAttemptsTimeFilter} <= 30, format_datetime(TimeBucket, 'yyyy-MM-dd'),\r\n {UpdateAttemptsTimeFilter} <= 90, strcat('Week of ', format_datetime(TimeBucket, 'yyyy-MM-dd')),\r\n format_datetime(TimeBucket, 'yyyy-MM')\r\n)\r\n| project TimeLabel, Succeeded, Failed, InProgress", "size": 0, "title": "Update Attempts by Day", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "barchart", "chartSettings": { "xAxis": "TimeLabel", "yAxis": [ "Succeeded", "Failed", "InProgress" ], "showLegend": true, "seriesLabelSettings": [] } }, "name": "update-attempts-by-day-chart" }, { "type": 1, "content": { "json": "### πŸ“Š Update Analytics", "style": "upsell" }, "name": "update-analytics-header" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "extensibilityresources\r\n| where type == \"microsoft.azurestackhci/clusters/updates/updateruns\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| extend state = tostring(properties.state)\r\n| extend timeStarted = todatetime(properties.timeStarted)\r\n| extend timeEnded = todatetime(properties.lastUpdatedTime)\r\n| where timeStarted >= ago({UpdateAttemptsTimeFilter}d)\r\n| extend updateName = tostring(split(id, '/updates/')[1])\r\n| extend updateName = tostring(split(updateName, '/updateruns')[0])\r\n| extend solutionUpdate = tostring(split(updateName, '/')[0])\r\n| where '*' in ({SolutionUpdateFilter}) or solutionUpdate in ({SolutionUpdateFilter})\r\n| extend clusterIdParts = split(id, '/')\r\n| extend clusterName = tostring(clusterIdParts[8])\r\n| extend updateKey = strcat(clusterName, '|', updateName)\r\n| extend duration = iff(state == 'Succeeded' and isnotempty(timeEnded) and timeEnded > timeStarted, timeEnded - timeStarted, timespan(null))\r\n| extend durationMinutes = iff(isnotnull(duration), todouble(duration / 1m), double(null))\r\n| summarize \r\n TotalRuns = count(),\r\n SucceededRuns = countif(state == 'Succeeded'),\r\n FailedRuns = countif(state == 'Failed'),\r\n InProgressRuns = countif(state == 'InProgress'),\r\n AvgDurationMinutes = avg(durationMinutes),\r\n StdDevDurationMinutes = stdev(durationMinutes),\r\n MinDurationMinutes = min(durationMinutes),\r\n MaxDurationMinutes = max(durationMinutes),\r\n P95DurationMinutes = percentile(durationMinutes, 95),\r\n P99DurationMinutes = percentile(durationMinutes, 99),\r\n CompletedDurationCount = countif(isnotnull(durationMinutes))\r\n| extend SuccessRate = round(100.0 * SucceededRuns / TotalRuns, 1)\r\n| extend FailureRate = round(100.0 * FailedRuns / TotalRuns, 1)\r\n| extend AvgDurationFormatted = iff(isnotnull(AvgDurationMinutes), strcat(tolong(AvgDurationMinutes / 60), 'h ', tolong(AvgDurationMinutes % 60), 'm'), 'N/A')\r\n| extend StdDevFormatted = iff(isnotnull(StdDevDurationMinutes), strcat(tolong(StdDevDurationMinutes / 60), 'h ', tolong(StdDevDurationMinutes % 60), 'm'), 'N/A')\r\n| extend MinDurationFormatted = iff(isnotnull(MinDurationMinutes), strcat(tolong(MinDurationMinutes / 60), 'h ', tolong(MinDurationMinutes % 60), 'm'), 'N/A')\r\n| extend MaxDurationFormatted = iff(isnotnull(MaxDurationMinutes), strcat(tolong(MaxDurationMinutes / 60), 'h ', tolong(MaxDurationMinutes % 60), 'm'), 'N/A')\r\n| extend P95DurationFormatted = iff(isnotnull(P95DurationMinutes), strcat(tolong(P95DurationMinutes / 60), 'h ', tolong(P95DurationMinutes % 60), 'm'), 'N/A')\r\n| extend P99DurationFormatted = iff(isnotnull(P99DurationMinutes), strcat(tolong(P99DurationMinutes / 60), 'h ', tolong(P99DurationMinutes % 60), 'm'), 'N/A')\r\n| project \r\n ['Total Runs'] = TotalRuns,\r\n ['Succeeded'] = SucceededRuns,\r\n ['Failed'] = FailedRuns,\r\n ['In Progress'] = InProgressRuns,\r\n ['Success Rate'] = SuccessRate,\r\n ['Average Duration'] = AvgDurationFormatted,\r\n ['Standard Deviation'] = StdDevFormatted,\r\n ['95th Percentile'] = P95DurationFormatted,\r\n ['99th Percentile'] = P99DurationFormatted,\r\n ['Min Duration'] = MinDurationFormatted,\r\n ['Max Duration'] = MaxDurationFormatted", "size": 4, "title": "Overall Update Duration - Analytical Statistics (Succeeded Updates)", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "gridSettings": { "rowLimit": 2000, "formatters": [ { "columnMatch": "Total Runs", "formatter": 4, "formatOptions": { "palette": "blue" } }, { "columnMatch": "Succeeded", "formatter": 4, "formatOptions": { "palette": "green" } }, { "columnMatch": "Failed", "formatter": 4, "formatOptions": { "palette": "red" } }, { "columnMatch": "In Progress", "formatter": 4, "formatOptions": { "palette": "orange" } }, { "columnMatch": "Success Rate", "formatter": 18, "formatOptions": { "thresholdsOptions": "colors", "thresholdsGrid": [ { "operator": ">=", "thresholdValue": "90", "representation": "green", "text": "{0}{1}%" }, { "operator": ">=", "thresholdValue": "70", "representation": "yellow", "text": "{0}{1}%" }, { "operator": "Default", "thresholdValue": null, "representation": "red", "text": "{0}{1}%" } ] } } ] } }, "name": "update-duration-statistics" }, { "type": 1, "content": { "json": "πŸ’‘ **Tip:** Use the 'Time Period' and 'Solution Update' filters above 'Update Attempts by Day' to view Azure Local Update Insights for your environment.", "style": "upsell" }, "name": "update-filters-tip-2" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "extensibilityresources\r\n| where type == \"microsoft.azurestackhci/clusters/updates/updateruns\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| extend state = tostring(properties.state)\r\n| extend timeStarted = todatetime(properties.timeStarted)\r\n| extend timeEnded = todatetime(properties.lastUpdatedTime)\r\n| where timeStarted >= ago({UpdateAttemptsTimeFilter}d)\r\n| extend updateName = tostring(split(id, '/updates/')[1])\r\n| extend updateName = tostring(split(updateName, '/updateruns')[0])\r\n| extend solutionUpdate = tostring(split(updateName, '/')[0])\r\n| where '*' in ({SolutionUpdateFilter}) or solutionUpdate in ({SolutionUpdateFilter})\r\n| extend clusterIdParts = split(id, '/')\r\n| extend clusterName = tostring(clusterIdParts[8])\r\n| extend updateKey = strcat(clusterName, '|', updateName)\r\n| extend duration = iff(state == 'Succeeded' and isnotempty(timeEnded) and timeEnded > timeStarted, timeEnded - timeStarted, timespan(null))\r\n| extend durationMinutes = iff(isnotnull(duration), todouble(duration / 1m), double(null))\r\n| summarize \r\n TotalRuns = count(),\r\n SucceededRuns = countif(state == 'Succeeded'),\r\n FailedRuns = countif(state == 'Failed'),\r\n InProgressRuns = countif(state == 'InProgress'),\r\n AvgDurationMinutes = avg(durationMinutes),\r\n StdDevDurationMinutes = stdev(durationMinutes),\r\n MinDurationMinutes = min(durationMinutes),\r\n MaxDurationMinutes = max(durationMinutes),\r\n P95DurationMinutes = percentile(durationMinutes, 95),\r\n P99DurationMinutes = percentile(durationMinutes, 99),\r\n CompletedDurationCount = countif(isnotnull(durationMinutes))\r\n by solutionUpdate\r\n| extend SuccessRate = round(100.0 * SucceededRuns / TotalRuns, 1)\r\n| extend FailureRate = round(100.0 * FailedRuns / TotalRuns, 1)\r\n| extend AvgDurationFormatted = iff(isnotnull(AvgDurationMinutes), strcat(tolong(AvgDurationMinutes / 60), 'h ', tolong(AvgDurationMinutes % 60), 'm'), 'N/A')\r\n| extend StdDevFormatted = iff(isnotnull(StdDevDurationMinutes), strcat(tolong(StdDevDurationMinutes / 60), 'h ', tolong(StdDevDurationMinutes % 60), 'm'), 'N/A')\r\n| extend MinDurationFormatted = iff(isnotnull(MinDurationMinutes), strcat(tolong(MinDurationMinutes / 60), 'h ', tolong(MinDurationMinutes % 60), 'm'), 'N/A')\r\n| extend MaxDurationFormatted = iff(isnotnull(MaxDurationMinutes), strcat(tolong(MaxDurationMinutes / 60), 'h ', tolong(MaxDurationMinutes % 60), 'm'), 'N/A')\r\n| extend P95DurationFormatted = iff(isnotnull(P95DurationMinutes), strcat(tolong(P95DurationMinutes / 60), 'h ', tolong(P95DurationMinutes % 60), 'm'), 'N/A')\r\n| extend P99DurationFormatted = iff(isnotnull(P99DurationMinutes), strcat(tolong(P99DurationMinutes / 60), 'h ', tolong(P99DurationMinutes % 60), 'm'), 'N/A')\r\n| project \r\n ['Solution Update'] = solutionUpdate,\r\n ['Total Runs'] = TotalRuns,\r\n ['Succeeded'] = SucceededRuns,\r\n ['Failed'] = FailedRuns,\r\n ['In Progress'] = InProgressRuns,\r\n ['Success Rate'] = SuccessRate,\r\n ['Average Duration'] = AvgDurationFormatted,\r\n ['Standard Deviation'] = StdDevFormatted,\r\n ['95th Percentile'] = P95DurationFormatted,\r\n ['99th Percentile'] = P99DurationFormatted,\r\n ['Min Duration'] = MinDurationFormatted,\r\n ['Max Duration'] = MaxDurationFormatted\r\n| order by ['Solution Update'] desc", "size": 0, "title": "Update Duration Statistics by Solution Update", "showRefreshButton": true, "showExportToExcel": true, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "gridSettings": { "rowLimit": 2000, "formatters": [ { "columnMatch": "Solution Update", "formatter": 0, "formatOptions": { "customColumnWidthSetting": "auto" } }, { "columnMatch": "Total Runs", "formatter": 4, "formatOptions": { "palette": "blue" } }, { "columnMatch": "Succeeded", "formatter": 4, "formatOptions": { "palette": "green" } }, { "columnMatch": "Failed", "formatter": 4, "formatOptions": { "palette": "red" } }, { "columnMatch": "In Progress", "formatter": 4, "formatOptions": { "palette": "orange" } }, { "columnMatch": "Success Rate", "formatter": 18, "formatOptions": { "thresholdsOptions": "colors", "thresholdsGrid": [ { "operator": ">=", "thresholdValue": "90", "representation": "green", "text": "{0}{1}%" }, { "operator": ">=", "thresholdValue": "70", "representation": "yellow", "text": "{0}{1}%" }, { "operator": "Default", "thresholdValue": null, "representation": "red", "text": "{0}{1}%" } ] } } ] } }, "name": "update-duration-statistics-by-solution" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "extensibilityresources\r\n| where type == \"microsoft.azurestackhci/clusters/updates/updateruns\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| extend state = tostring(properties.state)\r\n| extend timeStarted = todatetime(properties.timeStarted)\r\n| where timeStarted >= ago({UpdateAttemptsTimeFilter}d)\r\n| extend updateName = tostring(split(id, '/updates/')[1])\r\n| extend updateName = tostring(split(updateName, '/updateruns')[0])\r\n| extend solutionUpdate = tostring(split(updateName, '/')[0])\r\n| where '*' in ({SolutionUpdateFilter}) or solutionUpdate in ({SolutionUpdateFilter})\r\n| extend clusterIdParts = split(id, '/')\r\n| extend clusterName = tostring(clusterIdParts[8])\r\n| extend updateKey = strcat(clusterName, '|', updateName)\r\n| order by updateKey asc, timeStarted asc\r\n| extend runNumber = row_number(1, updateKey != prev(updateKey))\r\n| summarize \r\n FirstRunState = take_anyif(state, runNumber == 1),\r\n HasSuccess = countif(state == 'Succeeded') > 0,\r\n HasFailure = countif(state == 'Failed') > 0,\r\n TotalRuns = count()\r\n by updateKey\r\n| extend UpdateOutcome = case(\r\n FirstRunState == 'Succeeded', 'First Time Success',\r\n HasSuccess and HasFailure, 'Resumed After Failure',\r\n HasSuccess and not(HasFailure), 'Succeeded (Multiple Runs)',\r\n FirstRunState == 'InProgress', 'In Progress',\r\n HasFailure and not(HasSuccess), 'Failed (Not Recovered)',\r\n 'Other'\r\n)\r\n| summarize Count = count() by UpdateOutcome\r\n| extend JoinKey = 1\r\n| join kind=inner (\r\n extensibilityresources\r\n | where type == \"microsoft.azurestackhci/clusters/updates/updateruns\"\r\n | where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n | extend timeStarted = todatetime(properties.timeStarted)\r\n | where timeStarted >= ago({UpdateAttemptsTimeFilter}d)\r\n | extend updateName = tostring(split(id, '/updates/')[1])\r\n | extend updateName = tostring(split(updateName, '/updateruns')[0])\r\n | extend solutionUpdate = tostring(split(updateName, '/')[0])\r\n | where '*' in ({SolutionUpdateFilter}) or solutionUpdate in ({SolutionUpdateFilter})\r\n | extend clusterIdParts = split(id, '/')\r\n | extend clusterName = tostring(clusterIdParts[8])\r\n | extend updateKey = strcat(clusterName, '|', updateName)\r\n | summarize by updateKey\r\n | count\r\n | project Total = Count, JoinKey = 1\r\n) on JoinKey\r\n| project UpdateOutcome, Count, Percentage = strcat(tostring(round(100.0 * Count / Total, 1)), '%')\r\n| order by Count desc", "size": 0, "title": "Updates - First Time Success Analysis", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "table", "gridSettings": { "rowLimit": 2000, "formatters": [ { "columnMatch": "UpdateOutcome", "formatter": 18, "formatOptions": { "thresholdsOptions": "icons", "thresholdsGrid": [ { "operator": "==", "thresholdValue": "First Time Success", "representation": "success", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "Resumed After Failure", "representation": "2", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "Succeeded (Multiple Runs)", "representation": "success", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "In Progress", "representation": "pending", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "Failed (Not Recovered)", "representation": "critical", "text": "{0}{1}" }, { "operator": "Default", "thresholdValue": null, "representation": "unknown", "text": "{0}{1}" } ] } }, { "columnMatch": "Count", "formatter": 4, "formatOptions": { "palette": "blue" } } ], "labelSettings": [ { "columnId": "UpdateOutcome", "label": "Outcome" }, { "columnId": "Count", "label": "Updates" }, { "columnId": "Percentage", "label": "Percentage" } ] } }, "customWidth": "50", "name": "update-success-analysis" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "extensibilityresources\r\n| where type == \"microsoft.azurestackhci/clusters/updates/updateruns\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| extend state = tostring(properties.state)\r\n| extend timeStarted = todatetime(properties.timeStarted)\r\n| where timeStarted >= ago({UpdateAttemptsTimeFilter}d)\r\n| extend updateName = tostring(split(id, '/updates/')[1])\r\n| extend updateName = tostring(split(updateName, '/updateruns')[0])\r\n| extend solutionUpdate = tostring(split(updateName, '/')[0])\r\n| where '*' in ({SolutionUpdateFilter}) or solutionUpdate in ({SolutionUpdateFilter})\r\n| extend clusterIdParts = split(id, '/')\r\n| extend clusterName = tostring(clusterIdParts[8])\r\n| extend updateKey = strcat(clusterName, '|', updateName)\r\n| order by updateKey asc, timeStarted asc\r\n| extend runNumber = row_number(1, updateKey != prev(updateKey))\r\n| summarize \r\n FirstRunState = take_anyif(state, runNumber == 1),\r\n HasSuccess = countif(state == 'Succeeded') > 0,\r\n HasFailure = countif(state == 'Failed') > 0,\r\n TotalRuns = count()\r\n by updateKey\r\n| extend UpdateOutcome = case(\r\n FirstRunState == 'Succeeded', 'First Time Success',\r\n HasSuccess and HasFailure, 'Resumed After Failure',\r\n HasSuccess and not(HasFailure), 'Succeeded (Multiple Runs)',\r\n FirstRunState == 'InProgress', 'In Progress',\r\n HasFailure and not(HasSuccess), 'Failed (Not Recovered)',\r\n 'Other'\r\n)\r\n| summarize Count = count() by UpdateOutcome\r\n| order by Count desc", "size": 0, "title": "Update Outcomes Distribution", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "piechart", "chartSettings": { "seriesLabelSettings": [] } }, "customWidth": "50", "name": "update-outcomes-pie" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "extensibilityresources\r\n| where type == \"microsoft.azurestackhci/clusters/updates/updateruns\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| extend state = tostring(properties.state)\r\n| extend timeStarted = todatetime(properties.timeStarted)\r\n| extend timeEnded = todatetime(properties.lastUpdatedTime)\r\n| where timeStarted >= ago({UpdateAttemptsTimeFilter}d)\r\n| extend clusterId = tostring(split(id, '/providers/Microsoft.AzureStackHCI/clusters/')[0])\r\n| extend clusterIdFull = strcat(clusterId, '/providers/Microsoft.AzureStackHCI/clusters/', tostring(split(id, '/providers/Microsoft.AzureStackHCI/clusters/')[1]))\r\n| extend clusterIdParts = split(clusterIdFull, '/')\r\n| extend clusterName = tostring(clusterIdParts[8])\r\n| extend clusterResourceId = strcat('/subscriptions/', tostring(clusterIdParts[2]), '/resourceGroups/', tostring(clusterIdParts[4]), '/providers/Microsoft.AzureStackHCI/clusters/', clusterName)\r\n| extend clusterLink = strcat('https://portal.azure.com/#@/resource', clusterResourceId)\r\n| extend updateName = tostring(split(id, '/updates/')[1])\r\n| extend updateName = tostring(split(updateName, '/updateruns')[0])\r\n| extend solutionUpdate = tostring(split(updateName, '/')[0])\r\n| where '*' in ({SolutionUpdateFilter}) or solutionUpdate in ({SolutionUpdateFilter})\r\n| extend updateRunName = tostring(split(id, '/updateruns/')[1])\r\n| extend encodedClusterResourceId = replace_string(replace_string(clusterResourceId, '/', '%2F'), ' ', '%20')\r\n| extend updateRunLink = strcat('https://portal.azure.com/#view/Microsoft_AzureStackHCI_PortalExtension/SingleInstanceHistoryDetails.ReactView/resourceId/', encodedClusterResourceId, '/updateName/', updateName, '/updateRunName/', updateRunName, '/refresh~/false')\r\n| extend duration = iff(isnotempty(timeEnded) and timeEnded > timeStarted, timeEnded - timeStarted, timespan(null))\r\n| extend durationMinutes = iff(isnotnull(duration), tolong(duration / 1m), long(null))\r\n| extend durationFormatted = iff(isnotnull(durationMinutes), strcat(durationMinutes / 60, 'h ', durationMinutes % 60, 'm'), '')\r\n| project ClusterName = clusterName, ClusterLink = clusterLink, State = state, SolutionUpdate = solutionUpdate, UpdateName = updateName, UpdateRunLink = updateRunLink, Duration = durationFormatted, Started = timeStarted, Ended = timeEnded, ResourceGroup = resourceGroup\r\n| order by Started desc", "size": 0, "title": "Update Attempts Details", "showRefreshButton": true, "showExportToExcel": true, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "gridSettings": { "rowLimit": 2000, "formatters": [ { "columnMatch": "ClusterName", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkColumn": "ClusterLink" } }, { "columnMatch": "ClusterLink", "formatter": 5 }, { "columnMatch": "UpdateName", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkColumn": "UpdateRunLink" } }, { "columnMatch": "UpdateRunLink", "formatter": 5 }, { "columnMatch": "State", "formatter": 18, "formatOptions": { "thresholdsOptions": "icons", "thresholdsGrid": [ { "operator": "==", "thresholdValue": "Succeeded", "representation": "success", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "Failed", "representation": "critical", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "InProgress", "representation": "pending", "text": "{0}{1}" }, { "operator": "Default", "thresholdValue": null, "representation": "unknown", "text": "{0}{1}" } ] } }, { "columnMatch": "Started", "formatter": 6 }, { "columnMatch": "Ended", "formatter": 6 } ], "filter": true, "sortBy": [ { "itemKey": "Started", "sortOrder": 2 } ], "labelSettings": [ { "columnId": "ClusterName", "label": "Cluster Name" }, { "columnId": "ClusterLink" }, { "columnId": "State", "label": "State" }, { "columnId": "SolutionUpdate", "label": "Solution Update" }, { "columnId": "UpdateName", "label": "Update Run" }, { "columnId": "UpdateRunLink" }, { "columnId": "Duration", "label": "Duration" }, { "columnId": "Started", "label": "Started" }, { "columnId": "Ended", "label": "Ended" }, { "columnId": "ResourceGroup", "label": "Resource Group" } ] }, "sortBy": [ { "itemKey": "Started", "sortOrder": 2 } ] }, "name": "update-attempts-details-table" }, { "type": 1, "content": { "json": "πŸ’‘ **Tip:** Hover your mouse cursor over the pie chart to show the percentage for each status.", "style": "upsell" }, "name": "pie-chart-hover-tip" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "extensibilityresources\r\n| where type == \"microsoft.azurestackhci/clusters/updates/updateruns\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| extend state = tostring(properties.state)\r\n| extend timeStarted = todatetime(properties.timeStarted)\r\n| where timeStarted >= ago({UpdateAttemptsTimeFilter}d)\r\n| extend updateName = tostring(split(id, '/updates/')[1])\r\n| extend updateName = tostring(split(updateName, '/updateruns')[0])\r\n| extend solutionUpdate = tostring(split(updateName, '/')[0])\r\n| where '*' in ({SolutionUpdateFilter}) or solutionUpdate in ({SolutionUpdateFilter})\r\n| summarize Count = count() by state\r\n| order by Count desc", "size": 0, "title": "Update Attempts by Status Percentages", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "visualization": "piechart", "chartSettings": { "showLegend": true, "legendPosition": "bottom", "seriesLabelSettings": [] } }, "customWidth": "50", "name": "update-attempts-status-pie" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "extensibilityresources\r\n| where type == \"microsoft.azurestackhci/clusters/updates/updateruns\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| extend state = tostring(properties.state)\r\n| extend timeStarted = todatetime(properties.timeStarted)\r\n| where timeStarted >= ago({UpdateAttemptsTimeFilter}d)\r\n| extend updateName = tostring(split(id, '/updates/')[1])\r\n| extend updateName = tostring(split(updateName, '/updateruns')[0])\r\n| extend solutionUpdate = tostring(split(updateName, '/')[0])\r\n| where '*' in ({SolutionUpdateFilter}) or solutionUpdate in ({SolutionUpdateFilter})\r\n| summarize Total = count(), Succeeded = countif(state == 'Succeeded'), Failed = countif(state == 'Failed')\r\n| project Status = 'Succeeded', Count = Succeeded, Percentage = round(todouble(Succeeded) / todouble(Total) * 100, 1)\r\n| union (\r\n extensibilityresources\r\n | where type == \"microsoft.azurestackhci/clusters/updates/updateruns\"\r\n | where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n | extend state = tostring(properties.state)\r\n | extend timeStarted = todatetime(properties.timeStarted)\r\n | where timeStarted >= ago({UpdateAttemptsTimeFilter}d)\r\n | extend updateName = tostring(split(id, '/updates/')[1])\r\n | extend updateName = tostring(split(updateName, '/updateruns')[0])\r\n | extend solutionUpdate = tostring(split(updateName, '/')[0])\r\n | where '*' in ({SolutionUpdateFilter}) or solutionUpdate in ({SolutionUpdateFilter})\r\n | summarize Total = count(), Succeeded = countif(state == 'Succeeded'), Failed = countif(state == 'Failed')\r\n | project Status = 'Failed', Count = Failed, Percentage = round(todouble(Failed) / todouble(Total) * 100, 1)\r\n)\r\n| order by Status desc", "size": 4, "title": "Success / Failure Summary", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "gridSettings": { "rowLimit": 2000, "formatters": [ { "columnMatch": "Status", "formatter": 18, "formatOptions": { "thresholdsOptions": "icons", "thresholdsGrid": [ { "operator": "==", "thresholdValue": "Succeeded", "representation": "success", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "Failed", "representation": "critical", "text": "{0}{1}" }, { "operator": "Default", "thresholdValue": null, "text": "{0}{1}" } ] } }, { "columnMatch": "Percentage", "formatter": 0, "numberFormat": { "unit": 1, "options": { "style": "decimal", "minimumFractionDigits": 1, "maximumFractionDigits": 1 } } } ], "labelSettings": [ { "columnId": "Status", "label": "Status" }, { "columnId": "Count", "label": "Count" }, { "columnId": "Percentage", "label": "Overall Percentage" } ] } }, "customWidth": "25", "name": "update-attempts-summary-table" }, { "type": 1, "content": { "json": "## πŸ”„ All Cluster Update Status\r\n\r\nπŸ’‘ **Information:** Apply updates to keep your clusters within the six month support window." }, "name": "text-all-update-status" }, { "type": 9, "content": { "version": "KqlParameterItem/1.0", "parameters": [ { "id": "cluster-status-name-filter", "version": "KqlParameterItem/1.0", "name": "ClusterStatusNameFilter", "label": "Filter by Cluster Name", "type": 1, "description": "Optional: Filter by cluster name using wildcards. Example: *prod* or *hci*", "isRequired": false, "value": "", "timeContext": { "durationMs": 86400000 } }, { "id": "cluster-update-status-filter", "version": "KqlParameterItem/1.0", "name": "ClusterUpdateStatusFilter", "label": "Filter by Update Status", "type": 2, "isRequired": true, "multiSelect": true, "quote": "'", "delimiter": ",", "typeSettings": { "additionalResourceOptions": [ "value::all" ], "showDefault": false }, "jsonData": "[\r\n { \"value\": \"Up to date\", \"label\": \"Up to date\" },\r\n { \"value\": \"Update(s) available\", \"label\": \"Update(s) available\" },\r\n { \"value\": \"In progress\", \"label\": \"In progress\" },\r\n { \"value\": \"Failed to update\", \"label\": \"Failed to update\" },\r\n { \"value\": \"Action required\", \"label\": \"Action required\" },\r\n { \"value\": \"Unknown\", \"label\": \"Unknown\" }\r\n]", "defaultValue": "value::all", "timeContext": { "durationMs": 86400000 } }, { "id": "cluster-health-status-filter", "version": "KqlParameterItem/1.0", "name": "ClusterHealthStatusFilter", "label": "Filter by Health Status", "type": 2, "isRequired": true, "multiSelect": true, "quote": "'", "delimiter": ",", "typeSettings": { "additionalResourceOptions": [ "value::all" ], "showDefault": false }, "jsonData": "[\r\n { \"value\": \"Healthy\", \"label\": \"Healthy\" },\r\n { \"value\": \"Warning\", \"label\": \"Warning\" },\r\n { \"value\": \"Critical\", \"label\": \"Critical\" },\r\n { \"value\": \"Health check failed\", \"label\": \"Health check failed\" },\r\n { \"value\": \"In progress\", \"label\": \"In progress\" },\r\n { \"value\": \"Unknown\", \"label\": \"Unknown\" }\r\n]", "defaultValue": "value::all", "timeContext": { "durationMs": 86400000 } } ], "style": "pills", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces" }, "name": "cluster-status-filters" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "extensibilityresources\r\n| where type =~ 'microsoft.azurestackhci/clusters/updateSummaries'\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| parse id with id \"/updateSummaries/default\"\r\n| parse id with * \"/clusters/\" name\r\n| extend lowercase_id = tolower(id)\r\n| extend state = tostring(properties.state), healthState = tostring(properties.healthState)\r\n| extend updateStatus = case(\r\n state =~ 'Unknown', 'Unknown',\r\n state =~ 'AppliedSuccessfully', 'Up to date',\r\n state =~ 'UpdateAvailable', 'Update(s) available',\r\n state =~ 'UpdateInProgress', 'In progress',\r\n state =~ 'UpdateFailed', 'Failed to update',\r\n state =~ 'NeedsAttention', 'Failed to update',\r\n state =~ 'PreparationInProgress', 'In progress',\r\n state =~ 'PreparationFailed', 'Action required', 'Unknown'\r\n )\r\n| extend healthStatus = case(\r\n healthState =~ 'Unknown', 'Unknown',\r\n healthState =~ 'Success', 'Healthy',\r\n healthState =~ 'Failure', 'Critical',\r\n healthState =~ 'Warning', 'Warning',\r\n healthState =~ 'Error', 'Health check failed',\r\n healthState =~ 'InProgress', 'In progress', 'Unknown'\r\n )\r\n| extend currentVersion = tostring(properties.currentVersion)\r\n| extend lastUpdated = todatetime(properties.lastUpdated)\r\n| extend lastChecked = todatetime(properties.lastChecked)\r\n| extend oemFamily = tostring(properties.oemFamily)\r\n| extend hardwareModel = tostring(properties.hardwareModel)\r\n| extend packageVersions = properties.packageVersions\r\n| mv-expand pkg = packageVersions\r\n| extend pkgType = tostring(pkg.packageType)\r\n| extend pkgVersion = tostring(pkg.version)\r\n| summarize sbeVersion = maxif(pkgVersion, pkgType =~ 'SBE') by id, name, lowercase_id, state, healthState, updateStatus, healthStatus, currentVersion, lastUpdated, lastChecked, oemFamily, hardwareModel, resourceGroup, subscriptionId\r\n| where state in ('Unknown', 'AppliedSuccessfully', 'UpdateAvailable', 'UpdateInProgress', 'UpdateFailed', 'PreparationFailed', 'PreparationInProgress', 'NeedsAttention')\r\n| where healthState in ('Unknown', 'Success', 'Failure', 'Warning', 'Error', 'InProgress')\r\n| join kind=inner (\r\n resources\r\n | where type =~ 'microsoft.azurestackhci/clusters'\r\n | where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | where properties.reportedProperties.supportedCapabilities has 'CloudManagedUpdates'\r\n | extend azureConnection = case(\r\n properties.status =~ 'NotSpecified', 'Unknown',\r\n properties.status =~ 'ConnectedRecently', 'Connected',\r\n properties.status =~ 'NotConnectedRecently', 'Not connected recently',\r\n properties.status =~ 'NotYetRegistered', 'Not yet registered',\r\n properties.status =~ 'Error', 'Error',\r\n properties.status =~ 'Disconnected', 'Disconnected',\r\n properties.status =~ 'ValidationInProgress', 'Validation in progress',\r\n properties.status =~ 'ValidationSuccess', 'Validation success',\r\n properties.status =~ 'ValidationFailed', 'Validation failed',\r\n properties.status =~ 'DeploymentInProgress', 'Deployment in progress',\r\n properties.status =~ 'DeploymentSuccess', 'Deployment success',\r\n properties.status =~ 'DeploymentFailed', 'Deployment failed',\r\n 'Unknown'\r\n )\r\n | project clusterId = tolower(id), clusterTags = tags, azureConnection, clusterConnectionStatus = properties.status)\r\n on $left.lowercase_id == $right.clusterId\r\n| extend clusterLink = strcat('https://portal.azure.com/#@/resource', id, '/updates')\r\n| extend DaysSinceUpdate = datetime_diff('day', now(), lastUpdated)\r\n| where '{ClusterStatusNameFilter}' == '' or name matches regex strcat('(?i)', replace_string(replace_string('{ClusterStatusNameFilter}', '*', '.*'), '?', '.'))\r\n| where '*' in ({ClusterUpdateStatusFilter}) or updateStatus in ({ClusterUpdateStatusFilter})\r\n| where '*' in ({ClusterHealthStatusFilter}) or healthStatus in ({ClusterHealthStatusFilter})\r\n| project ClusterName = name, clusterLink, UpdateStatus = updateStatus, HealthStatus = healthStatus, CurrentVersion = currentVersion, AzureConnection = azureConnection, SBEVersion = coalesce(sbeVersion, ''), DaysSinceUpdate, LastUpdated = lastUpdated, LastChecked = lastChecked, OEMFamily = oemFamily, HardwareModel = hardwareModel, State = state, HealthState = healthState, resourceGroup, subscriptionId\r\n| order by DaysSinceUpdate desc, ClusterName asc", "size": 0, "showAnalytics": true, "showRefreshButton": true, "showExportToExcel": true, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "gridSettings": { "rowLimit": 2000, "formatters": [ { "columnMatch": "clusterLink", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkLabel": "View History" } }, { "columnMatch": "UpdateStatus", "formatter": 18, "formatOptions": { "thresholdsOptions": "icons", "thresholdsGrid": [ { "operator": "==", "thresholdValue": "Up to date", "representation": "success", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "In progress", "representation": "pending", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "Update(s) available", "representation": "2", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "Failed to update", "representation": "4", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "Action required", "representation": "4", "text": "{0}{1}" }, { "operator": "Default", "thresholdValue": null, "representation": "unknown", "text": "{0}{1}" } ] } }, { "columnMatch": "HealthStatus", "formatter": 18, "formatOptions": { "thresholdsOptions": "icons", "thresholdsGrid": [ { "operator": "==", "thresholdValue": "Healthy", "representation": "success", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "Warning", "representation": "2", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "Critical", "representation": "4", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "Health check failed", "representation": "4", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "In progress", "representation": "pending", "text": "{0}{1}" }, { "operator": "Default", "thresholdValue": null, "representation": "unknown", "text": "{0}{1}" } ] } }, { "columnMatch": "AzureConnection", "formatter": 18, "formatOptions": { "thresholdsOptions": "icons", "thresholdsGrid": [ { "operator": "==", "thresholdValue": "Connected", "representation": "success", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "Disconnected", "representation": "4", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "Not connected recently", "representation": "2", "text": "{0}{1}" }, { "operator": "Default", "thresholdValue": null, "representation": "unknown", "text": "{0}{1}" } ] } }, { "columnMatch": "DaysSinceUpdate", "formatter": 18, "formatOptions": { "thresholdsOptions": "colors", "thresholdsGrid": [ { "operator": ">=", "thresholdValue": "100", "representation": "redBright", "text": "{0}{1} days ago" }, { "operator": ">=", "thresholdValue": "60", "representation": "yellow", "text": "{0}{1} days ago" }, { "operator": "Default", "thresholdValue": null, "text": "{0}{1} days ago" } ] } }, { "columnMatch": "State", "formatter": 5 }, { "columnMatch": "HealthState", "formatter": 5 } ], "filter": true, "labelSettings": [ { "columnId": "ClusterName", "label": "Cluster Name" }, { "columnId": "clusterLink", "label": "Update History" }, { "columnId": "UpdateStatus", "label": "Update Status" }, { "columnId": "HealthStatus", "label": "Health Status" }, { "columnId": "AzureConnection", "label": "Azure Connection" }, { "columnId": "SBEVersion", "label": "SBE Version" }, { "columnId": "CurrentVersion", "label": "Current Version" }, { "columnId": "DaysSinceUpdate", "label": "Last Update Installed" }, { "columnId": "LastUpdated", "label": "Last Updated" }, { "columnId": "LastChecked", "label": "Last Checked" }, { "columnId": "resourceGroup", "label": "Resource Group" }, { "columnId": "location", "label": "Location" } ] } }, "name": "all-cluster-update-status" }, { "type": 1, "content": { "json": "## ⏳ Clusters Currently Updating" }, "name": "text-currently-updating" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "// Primary source: update runs with InProgress state\r\nextensibilityresources\r\n| where type == \"microsoft.azurestackhci/clusters/updates/updateruns\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| extend runState = tostring(properties.state)\r\n| where runState == \"InProgress\"\r\n| extend clusterId = tolower(substring(id, 0, indexof(id, '/updates/')))\r\n| extend clusterName = tostring(split(id, '/')[8])\r\n| extend clusterRG = tolower(resourceGroup)\r\n| extend updateName = tostring(split(id, '/updates/')[1])\r\n| extend updateName = tostring(split(updateName, '/updateRuns')[0])\r\n| join kind=leftouter (\r\n extensibilityresources\r\n | where type == \"microsoft.azurestackhci/clusters/updates/updateruns\"\r\n | extend _state = tostring(properties.state)\r\n | where _state == \"Succeeded\"\r\n | extend clusterId = tolower(substring(id, 0, indexof(id, '/updates/')))\r\n | extend updateName = tostring(split(id, '/updates/')[1])\r\n | extend updateName = tostring(split(updateName, '/updateRuns')[0])\r\n | project clusterId, updateName\r\n | summarize _succeededFlag = count() by clusterId, updateName\r\n) on clusterId, updateName\r\n| where isnull(_succeededFlag)\r\n| extend timeStarted = todatetime(properties.timeStarted)\r\n| extend progressObj = properties.progress\r\n| extend progressDescription = tostring(progressObj.description)\r\n| mv-expand s1 = progressObj.steps\r\n| mv-expand s2 = s1.steps\r\n| mv-expand s3 = s2.steps\r\n| mv-expand s4 = s3.steps\r\n| mv-expand s5 = s4.steps\r\n| mv-expand s6 = s5.steps\r\n| mv-expand s7 = s6.steps\r\n| extend s5Name = tostring(s5.name), s5Status = tostring(s5.status), s5Start = todatetime(s5.startTimeUtc)\r\n| extend s6Name = tostring(s6.name), s6Status = tostring(s6.status), s6Start = todatetime(s6.startTimeUtc)\r\n| extend s7Name = tostring(s7.name), s7Status = tostring(s7.status), s7Start = todatetime(s7.startTimeUtc)\r\n| extend s8Arr = s7.steps\r\n| extend s8First = iff(array_length(s8Arr) > 0, s8Arr[0], dynamic(null))\r\n| extend s8Name = tostring(s8First.name), s8Status = tostring(s8First.status), s8Start = todatetime(s8First.startTimeUtc)\r\n| extend s9Arr = s8First.steps\r\n| extend s9First = iff(array_length(s9Arr) > 0, s9Arr[0], dynamic(null))\r\n| extend s9Name = tostring(s9First.name), s9Status = tostring(s9First.status), s9Start = todatetime(s9First.startTimeUtc)\r\n| extend currentStepDepth = case(\r\n s9Status == 'InProgress', 9,\r\n s8Status == 'InProgress', 8,\r\n s7Status == 'InProgress', 7,\r\n s6Status == 'InProgress', 6,\r\n s5Status == 'InProgress', 5,\r\n 0)\r\n| extend currentStepName = case(\r\n currentStepDepth == 9, s9Name,\r\n currentStepDepth == 8, s8Name,\r\n currentStepDepth == 7, s7Name,\r\n currentStepDepth == 6, s6Name,\r\n currentStepDepth == 5, s5Name,\r\n '')\r\n| extend currentStepStart = case(\r\n currentStepDepth == 9, s9Start,\r\n currentStepDepth == 8, s8Start,\r\n currentStepDepth == 7, s7Start,\r\n currentStepDepth == 6, s6Start,\r\n currentStepDepth == 5, s5Start,\r\n datetime(null))\r\n| summarize \r\n arg_max(currentStepDepth, currentStepName, currentStepStart),\r\n timeStarted = max(timeStarted),\r\n progressDescription = max(progressDescription),\r\n updateName = take_any(updateName),\r\n clusterName = take_any(clusterName),\r\n clusterRG = take_any(clusterRG)\r\n by clusterId\r\n| extend CurrentStep = iff(isnotempty(currentStepName), currentStepName, progressDescription)\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterNameLower = tolower(name), clusterRG = tolower(resourceGroup), clusterIdFull = tolower(id)\r\n) on $left.clusterId == $right.clusterIdFull\r\n| join kind=leftouter (\r\n extensibilityresources\r\n | where type == \"microsoft.azurestackhci/clusters/updatesummaries\"\r\n | extend summaryClusterId = tolower(substring(id, 0, indexof(id, '/updateSummaries/')))\r\n | extend summaryState = tostring(properties.state)\r\n | extend currentVersion = tostring(properties.currentVersion)\r\n | extend lastUpdated = todatetime(properties.lastUpdated)\r\n | extend lastChecked = todatetime(properties.lastChecked)\r\n | project summaryClusterId, summaryState, currentVersion, lastUpdated, lastChecked\r\n) on $left.clusterId == $right.summaryClusterId\r\n| extend clusterLink = strcat('https://portal.azure.com/#@/resource', clusterId, '/updates')\r\n| extend clusterIdEncoded = replace_string(clusterId, '/', '%2F')\r\n| extend updateRunLink = strcat('https://portal.azure.com/#view/Microsoft_AzureStackHCI_PortalExtension/SingleInstanceHistoryDetails.ReactView/resourceId/', clusterIdEncoded, '/updateName~/null/updateRunName~/null/refresh~/false')\r\n| extend _stepDur = iff(isnotempty(currentStepStart), now() - currentStepStart, timespan(null))\r\n| extend StepDuration = case(\r\n isnull(_stepDur), '',\r\n _stepDur >= 1d, strcat(toint(_stepDur / 1d), 'd ', toint((_stepDur % 1d) / 1h), 'h ', toint((_stepDur % 1h) / 1m), 'm'),\r\n _stepDur >= 1h, strcat(toint(_stepDur / 1h), 'h ', toint((_stepDur % 1h) / 1m), 'm'),\r\n strcat(toint(_stepDur / 1m), 'm'))\r\n| project ClusterName = clusterName, clusterLink, UpdateInstalling = updateName, State = coalesce(summaryState, 'InProgress'), CurrentStep, StepDuration, updateRunLink, CurrentVersion = coalesce(currentVersion, ''), LastUpdated = lastUpdated, LastChecked = lastChecked\r\n| order by ClusterName asc", "size": 0, "showRefreshButton": true, "showExportToExcel": true, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "gridSettings": { "rowLimit": 2000, "formatters": [ { "columnMatch": "ClusterName", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkColumn": "clusterLink" } }, { "columnMatch": "clusterLink", "formatter": 5 }, { "columnMatch": "State", "formatter": 18, "formatOptions": { "thresholdsOptions": "icons", "thresholdsGrid": [ { "operator": "contains", "thresholdValue": "Progress", "representation": "pending", "text": "{0}{1}" }, { "operator": "Default", "thresholdValue": null, "representation": "pending", "text": "{0}{1}" } ] } }, { "columnMatch": "updateRunLink", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkLabel": "View Progress" } } ], "labelSettings": [ { "columnId": "ClusterName", "label": "Cluster Name" }, { "columnId": "clusterLink" }, { "columnId": "UpdateInstalling", "label": "Update Installing" }, { "columnId": "State", "label": "State" }, { "columnId": "CurrentStep", "label": "Current Step" }, { "columnId": "StepDuration", "label": "Step Duration" }, { "columnId": "updateRunLink", "label": "Update Run" }, { "columnId": "CurrentVersion", "label": "Current Version" }, { "columnId": "LastUpdated", "label": "Last Updated" }, { "columnId": "LastChecked", "label": "Last Checked" } ] } }, "name": "clusters-updating" }, { "type": 1, "content": { "json": "πŸ“š **Knowledge:** [Troubleshoot Update failures](https://learn.microsoft.com/azure/azure-local/update/update-troubleshooting-23h2#troubleshoot-update-failures)" }, "name": "update-failures-knowledge-link" }, { "type": 1, "content": { "json": "## πŸ“¦ Clusters with Updates Available\r\n\r\nπŸ’‘ **Information:** [Azure Local Known Issues documentation](https://learn.microsoft.com/azure/azure-local/known-issues)" }, "name": "text-updates-available" }, { "type": 9, "content": { "version": "KqlParameterItem/1.0", "parameters": [ { "id": "updates-available-filter", "version": "KqlParameterItem/1.0", "name": "UpdatesAvailableFilter", "label": "Filter by Update Available", "type": 2, "description": "Filter clusters by available update version", "isRequired": false, "multiSelect": true, "quote": "'", "delimiter": ",", "query": "extensibilityresources\r\n| where type == \"microsoft.azurestackhci/clusters/updates\"\r\n| extend updateState = tostring(properties.state)\r\n| where updateState in ('Ready', 'ReadyToInstall', 'AdditionalContentRequired', 'HasPrerequisite', 'HealthCheckFailed', 'Downloading', 'Preparing', 'HealthChecking')\r\n| distinct name\r\n| order by name desc", "crossComponentResources": [ "{Subscriptions}" ], "typeSettings": { "additionalResourceOptions": [ "value::all" ], "showDefault": false }, "defaultValue": "value::all", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources" } ], "style": "pills", "queryType": 1, "resourceType": "microsoft.resourcegraph/resources" }, "name": "updates-available-filters" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "extensibilityresources\r\n| where type == \"microsoft.azurestackhci/clusters/updatesummaries\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| extend clusterName = tostring(split(id, '/')[8])\r\n| extend clusterRG = resourceGroup\r\n| join kind=inner (\r\n resources\r\n | where type == \"microsoft.azurestackhci/clusters\"\r\n | where '{ClusterTagName}' == '' or ('{ClusterTagValue}' != '' and tostring(tags['{ClusterTagName}']) =~ '{ClusterTagValue}')\r\n | project clusterName = name, clusterRG = resourceGroup\r\n) on clusterName, clusterRG\r\n| join kind=inner (\r\n extensibilityresources\r\n | where type == \"microsoft.azurestackhci/clusters/updates\"\r\n | where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n | extend updateClusterName = tostring(split(id, '/')[8])\r\n | extend updateState = tostring(properties.state)\r\n | where updateState in ('Ready', 'ReadyToInstall', 'AdditionalContentRequired', 'HasPrerequisite', 'HealthCheckFailed', 'Downloading', 'Preparing', 'HealthChecking')\r\n | where '{UpdatesAvailableFilter:label}' == 'All' or name in~ ({UpdatesAvailableFilter})\r\n | summarize AvailableUpdates = make_list(name), UpdateStates = make_list(updateState) by updateClusterName\r\n | extend UpdateAvailable = strcat_array(AvailableUpdates, ', ')\r\n | extend UpdateState = strcat_array(UpdateStates, ', ')\r\n | project updateClusterName, UpdateAvailable, UpdateState\r\n) on $left.clusterName == $right.updateClusterName\r\n| extend state = tostring(properties.state)\r\n| extend currentVersion = tostring(properties.currentVersion)\r\n| extend lastChecked = todatetime(properties.lastChecked)\r\n| extend clusterId = substring(id, 0, indexof(id, '/updateSummaries/'))\r\n| extend packageVersions = properties.packageVersions\r\n| mv-expand pkg = packageVersions\r\n| extend pkgType = tostring(pkg.packageType)\r\n| extend pkgVersion = tostring(pkg.version)\r\n| summarize sbeVersion = maxif(pkgVersion, pkgType =~ 'SBE') by clusterName, state, currentVersion, lastChecked, clusterId, resourceGroup, UpdateAvailable, UpdateState\r\n| where state == \"UpdateAvailable\"\r\n| extend clusterLink = strcat('https://portal.azure.com/#@/resource', clusterId, '/updates')\r\n| extend clusterIdEncoded = replace_string(replace_string(replace_string(clusterId, '/', '%2F'), '\"', '%22'), ' ', '%20')\r\n| extend clusterIdsJson = strcat('%5B%7B%22id%22%3A%22', clusterIdEncoded, '%22%2C%22oemFamily%22%3A%22%22%7D%5D')\r\n| extend oneTimeUpdateLink = strcat('https://portal.azure.com/#view/Microsoft_AzureStackHCI_PortalExtension/InstallUpdates.ReactView/clusterIds~/', clusterIdsJson)\r\n| project ClusterName = clusterName, clusterLink, CurrentVersion = currentVersion, CurrentSBEVersion = coalesce(sbeVersion, ''), UpdateAvailable, UpdateState, LastChecked = lastChecked, oneTimeUpdateLink, resourceGroup\r\n| order by ClusterName asc", "size": 0, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ] }, "conditionalVisibility": { "parameterName": "AlwaysHidden", "comparison": "isEqualTo", "value": "__never_visible__" }, "name": "updates-available-base" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "extensibilityresources\r\n| where type == \"microsoft.azurestackhci/clusters/updates\"\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| extend updateClusterName = tostring(split(id, '/')[8])\r\n| extend packageType = tostring(properties.packageType)\r\n| where packageType =~ 'SBE'\r\n| extend updateState = tostring(properties.state)\r\n| where updateState in ('AdditionalContentRequired', 'HasPrerequisite')\r\n| extend sbeVersionRaw = tostring(properties.version)\r\n| extend sbeUpdateVersion = strcat('⚠️ ', sbeVersionRaw)\r\n| extend additionalProps = todynamic(tostring(properties.additionalProperties))\r\n| extend sbePublisher = tostring(additionalProps.SBEPublisher)\r\n| extend sbeFamily = tostring(additionalProps.SBEFamily)\r\n| extend sbeReleaseLink = tostring(additionalProps.SBEReleaseLink)\r\n| extend DependencyInfo = strcat('## SBE Update Details\\n\\n| Property | Value |\\n|:--|:--|\\n| **Publisher** | ', coalesce(sbePublisher, 'N/A'), ' |\\n| **Family** | ', coalesce(sbeFamily, 'N/A'), ' |\\n| **Version** | ', coalesce(sbeVersionRaw, 'N/A'), ' |\\n| **Release Notes** | ', iff(isnotempty(sbeReleaseLink), strcat('[View Release Notes](', sbeReleaseLink, ')'), 'N/A'), ' |')\r\n| project updateClusterName, sbeUpdateVersion, sbePublisher, sbeFamily, sbeReleaseLink, DependencyInfo\r\n| summarize sbeUpdateVersion = max(sbeUpdateVersion), sbePublisher = max(sbePublisher), sbeFamily = max(sbeFamily), sbeReleaseLink = max(sbeReleaseLink), DependencyInfo = max(DependencyInfo) by updateClusterName", "size": 0, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ] }, "conditionalVisibility": { "parameterName": "AlwaysHidden", "comparison": "isEqualTo", "value": "__never_visible__" }, "name": "updates-available-sbe" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "{\"version\":\"Merge/1.0\",\"merges\":[{\"id\":\"updates-available-merge\",\"mergeType\":\"leftouter\",\"leftTable\":\"updates-available-base\",\"rightTable\":\"updates-available-sbe\",\"leftColumn\":\"ClusterName\",\"rightColumn\":\"updateClusterName\"}],\"projectRename\":[{\"originalName\":\"[updates-available-base].ClusterName\",\"mergedName\":\"ClusterName\",\"fromId\":\"updates-available-merge\"},{\"originalName\":\"[updates-available-base].clusterLink\",\"mergedName\":\"clusterLink\",\"fromId\":\"updates-available-merge\"},{\"originalName\":\"[updates-available-base].CurrentVersion\",\"mergedName\":\"CurrentVersion\",\"fromId\":\"updates-available-merge\"},{\"originalName\":\"[updates-available-base].CurrentSBEVersion\",\"mergedName\":\"CurrentSBEVersion\",\"fromId\":\"updates-available-merge\"},{\"originalName\":\"[updates-available-base].UpdateAvailable\",\"mergedName\":\"UpdateAvailable\",\"fromId\":\"updates-available-merge\"},{\"originalName\":\"[updates-available-base].UpdateState\",\"mergedName\":\"UpdateState\",\"fromId\":\"updates-available-merge\"},{\"originalName\":\"[updates-available-sbe].sbeUpdateVersion\",\"mergedName\":\"DependencyLabel\",\"fromId\":\"updates-available-merge\"},{\"originalName\":\"[updates-available-sbe].DependencyInfo\",\"mergedName\":\"DependencyInfo\",\"fromId\":\"updates-available-merge\"},{\"originalName\":\"[updates-available-sbe].sbePublisher\",\"mergedName\":\"sbePublisher\",\"fromId\":\"updates-available-merge\"},{\"originalName\":\"[updates-available-sbe].sbeFamily\",\"mergedName\":\"sbeFamily\",\"fromId\":\"updates-available-merge\"},{\"originalName\":\"[updates-available-sbe].sbeReleaseLink\",\"mergedName\":\"sbeReleaseLink\",\"fromId\":\"updates-available-merge\"},{\"originalName\":\"[updates-available-base].LastChecked\",\"mergedName\":\"LastChecked\",\"fromId\":\"updates-available-merge\"},{\"originalName\":\"[updates-available-base].oneTimeUpdateLink\",\"mergedName\":\"oneTimeUpdateLink\",\"fromId\":\"updates-available-merge\"},{\"originalName\":\"[updates-available-base].resourceGroup\",\"mergedName\":\"resourceGroup\",\"fromId\":\"updates-available-merge\"},{\"originalName\":\"[updates-available-sbe].updateClusterName\"}]}", "size": 0, "showRefreshButton": true, "showExportToExcel": true, "queryType": 7, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "gridSettings": { "rowLimit": 2000, "formatters": [ { "columnMatch": "clusterLink", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkLabel": "View Updates" } }, { "columnMatch": "DependencyLabel", "formatter": 7, "formatOptions": { "linkTarget": "CellDetails", "linkIsContextBlade": true, "linkColumn": "DependencyInfo", "customColumnWidthSetting": "auto" }, "tooltipFormat": { "tooltip": "Click for SBE update details" } }, { "columnMatch": "DependencyInfo", "formatter": 5 }, { "columnMatch": "sbePublisher", "formatter": 5 }, { "columnMatch": "sbeFamily", "formatter": 5 }, { "columnMatch": "sbeReleaseLink", "formatter": 5 }, { "columnMatch": "oneTimeUpdateLink", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkLabel": "πŸ”„ Install Update" } } ], "filter": true, "labelSettings": [ { "columnId": "ClusterName", "label": "Cluster Name" }, { "columnId": "clusterLink", "label": "Available Updates" }, { "columnId": "CurrentVersion", "label": "Current Version" }, { "columnId": "CurrentSBEVersion", "label": "Current SBE Version" }, { "columnId": "UpdateAvailable", "label": "Update Available" }, { "columnId": "UpdateState", "label": "Update State" }, { "columnId": "DependencyLabel", "label": "Dependency Information" }, { "columnId": "LastChecked", "label": "Last Checked" }, { "columnId": "oneTimeUpdateLink", "label": "One Time Update" } ] } }, "name": "clusters-updates-available" }, { "type": 1, "content": { "json": "## πŸ“œ Update Run History and Error Details\n\nRecent update runs across all clusters. For failed updates, expand the row to see step failure details.\n\n> **Note:** Default filter applied to table below: 'Update State' = 'Failed'" }, "name": "text-update-history" }, { "type": 9, "content": { "version": "KqlParameterItem/1.0", "parameters": [ { "id": "update-history-cluster-filter", "version": "KqlParameterItem/1.0", "name": "UpdateHistoryClusterFilter", "label": "Filter by Cluster Name", "type": 1, "description": "Optional: Filter by cluster name using wildcards. Example: *prod* or *hci*", "isRequired": false, "value": "", "timeContext": { "durationMs": 86400000 } }, { "id": "update-history-update-name-filter", "version": "KqlParameterItem/1.0", "name": "UpdateHistoryUpdateNameFilter", "label": "Filter by Update Name", "type": 1, "description": "Optional: Filter by update name using wildcards. Example: *10.2411* or *SBE*", "isRequired": false, "value": "", "timeContext": { "durationMs": 86400000 } }, { "id": "update-history-state-filter", "version": "KqlParameterItem/1.0", "name": "UpdateHistoryStateFilter", "label": "Filter by Update State", "type": 2, "isRequired": true, "multiSelect": true, "quote": "'", "delimiter": ",", "typeSettings": { "additionalResourceOptions": [ "value::all" ], "showDefault": false }, "jsonData": "[\r\n { \"value\": \"Succeeded\", \"label\": \"Succeeded\" },\r\n { \"value\": \"Failed\", \"label\": \"Failed\" },\r\n { \"value\": \"InProgress\", \"label\": \"In Progress\" }\r\n]", "defaultValue": "Failed", "timeContext": { "durationMs": 86400000 } }, { "id": "update-history-status-filter", "version": "KqlParameterItem/1.0", "name": "UpdateHistoryStatusFilter", "label": "Filter by Status", "type": 2, "isRequired": true, "multiSelect": true, "quote": "'", "delimiter": ",", "typeSettings": { "additionalResourceOptions": [ "value::all" ], "showDefault": false }, "jsonData": "[\r\n { \"value\": \"Success\", \"label\": \"Success\" },\r\n { \"value\": \"Error\", \"label\": \"Error\" },\r\n { \"value\": \"InProgress\", \"label\": \"In Progress\" }\r\n]", "defaultValue": "value::all", "timeContext": { "durationMs": 86400000 } } ], "style": "pills", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces" }, "name": "update-history-filters" }, { "type": 3, "content": { "version": "KqlItem/1.0", "query": "extensibilityresources\r\n| where type == 'microsoft.azurestackhci/clusters/updates/updateruns'\r\n| where '{ResourceGroupFilter}' == '' or resourceGroup matches regex strcat('(?i)', replace_string(replace_string('{ResourceGroupFilter}', '*', '.*'), '?', '.'))\r\n| extend hciClusterName = tostring(split(id, '/')[8])\r\n| extend updateName = tostring(split(id, '/')[10])\r\n| extend runName = name\r\n| extend state = tostring(properties.state)\r\n| extend timeStarted = todatetime(properties.timeStarted)\r\n| extend lastUpdatedTime = todatetime(properties.lastUpdatedTime)\r\n| extend duration = tostring(properties.duration)\r\n| extend durationHours = iff(duration contains 'H', toint(extract('([0-9]+)H', 1, duration)), 0)\r\n| extend durationMinutes = iff(duration contains 'M', toint(extract('([0-9]+)M', 1, duration)), 0)\r\n| extend durationSeconds = iff(duration contains 'S', toint(extract('([0-9]+)', 1, extract('([0-9.]+)S', 1, duration))), 0)\r\n| extend durationFormatted = strcat(iff(durationHours > 0, strcat(tostring(durationHours), 'h '), ''), iff(durationMinutes > 0 or durationHours > 0, strcat(tostring(durationMinutes), 'm '), ''), tostring(durationSeconds), 's')\r\n| extend progressObj = properties.progress\r\n| extend progressJson = tostring(properties.progress)\r\n| extend progressStatus = tostring(progressObj.status)\r\n| extend progressDescription = tostring(progressObj.description)\r\n| mv-expand s1 = progressObj.steps\r\n| mv-expand s2 = s1.steps\r\n| mv-expand s3 = s2.steps\r\n| mv-expand s4 = s3.steps\r\n| mv-expand s5 = s4.steps\r\n| mv-expand s6 = s5.steps\r\n| mv-expand s7 = s6.steps\r\n| extend e5Msg = tostring(s5.errorMessage), e5Name = tostring(s5.name), e5Status = tostring(s5.status)\r\n| extend e6Msg = tostring(s6.errorMessage), e6Name = tostring(s6.name), e6Status = tostring(s6.status)\r\n| extend e7Msg = tostring(s7.errorMessage), e7Name = tostring(s7.name), e7Status = tostring(s7.status)\r\n| extend e8Arr = s7.steps\r\n| extend e8First = iff(array_length(e8Arr) > 0, e8Arr[0], dynamic(null))\r\n| extend e8Msg = tostring(e8First.errorMessage), e8Name = tostring(e8First.name), e8Status = tostring(e8First.status)\r\n| extend mvExpandErrMsg = coalesce(iff(strlen(e8Msg) > 0, e8Msg, ''), iff(strlen(e7Msg) > 0, e7Msg, ''), iff(strlen(e6Msg) > 0, e6Msg, ''), iff(strlen(e5Msg) > 0, e5Msg, ''))\r\n| extend deepExceptionMsg = extract('raised an exception:................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................', 0, progressJson)\r\n| extend deepestErrMsg = iff(strlen(mvExpandErrMsg) > 10, mvExpandErrMsg, deepExceptionMsg)\r\n| extend errorBasedStep = case(\r\n deepestErrMsg has 'UpdateSecuredCore', 'Update Secured-core',\r\n deepestErrMsg has 'CAU' or deepestErrMsg has 'Cluster-Aware', 'CAU Update',\r\n deepestErrMsg has 'RotateSecrets' or deepestErrMsg has 'Rotate Secrets', 'Rotate Secrets',\r\n deepestErrMsg has 'MocArb' or deepestErrMsg has 'CliExtensions', 'Update Arc Prerequisites',\r\n deepestErrMsg has 'certificate rotation', 'Certificate Rotation',\r\n '')\r\n| extend deepestErrStep = case(strlen(e8Msg) > 0, e8Name, strlen(e7Msg) > 0, e7Name, strlen(e6Msg) > 0, e6Name, strlen(e5Msg) > 0, e5Name, e8Status == 'Error', e8Name, e7Status == 'Error', e7Name, e6Status == 'Error', e6Name, e5Status == 'Error', e5Name, strlen(errorBasedStep) > 0, errorBasedStep, '')\r\n| summarize \r\n ErrorStepName = max(deepestErrStep),\r\n ErrorStepMessage = max(deepestErrMsg),\r\n state = max(state),\r\n timeStarted = max(timeStarted),\r\n lastUpdatedTime = max(lastUpdatedTime),\r\n durationFormatted = max(durationFormatted),\r\n progressStatus = max(progressStatus),\r\n progressDescription = max(progressDescription),\r\n subscriptionId = max(subscriptionId),\r\n resourceGroup = max(resourceGroup)\r\n by hciClusterName, updateName, id\r\n| extend CurrentStep = iff(state == 'Failed', iff(isnotempty(ErrorStepName), ErrorStepName, progressDescription), '')\r\n| extend ErrorMessageClean = replace_string(replace_string(ErrorStepMessage, '\\\\r\\\\n', ' '), '\\\\n', ' ')\r\n| extend ErrorMessageDisplay = iff(strlen(ErrorMessageClean) > 0, ErrorMessageClean, iff(state == 'Failed' and progressStatus == 'Error', 'No ''Error Details'' available - click Update Name link to view in Azure portal', ''))\r\n| extend ErrorMessageTruncated = iff(strlen(ErrorMessageDisplay) > 3000, strcat(substring(ErrorMessageDisplay, 0, 3000), '... (truncated)'), ErrorMessageDisplay)\r\n| where todatetime(timeStarted) >= {TimeRange:start}\r\n| where '{UpdateHistoryClusterFilter}' == '' or hciClusterName matches regex strcat('(?i)', replace_string(replace_string('{UpdateHistoryClusterFilter}', '*', '.*'), '?', '.'))\r\n| where '{UpdateHistoryUpdateNameFilter}' == '' or updateName matches regex strcat('(?i)', replace_string(replace_string('{UpdateHistoryUpdateNameFilter}', '*', '.*'), '?', '.'))\r\n| where '*' in ({UpdateHistoryStateFilter}) or state in ({UpdateHistoryStateFilter})\r\n| where '*' in ({UpdateHistoryStatusFilter}) or progressStatus in ({UpdateHistoryStatusFilter})\r\n| extend hasErrorDetails = iff(isnotempty(ErrorStepMessage), 1, 0)\r\n| extend sortKey = strcat(tostring(hasErrorDetails), '_', format_datetime(timeStarted, 'yyyy-MM-dd HH:mm:ss'))\r\n| extend clusterResourceId = strcat('/subscriptions/', subscriptionId, '/resourceGroups/', resourceGroup, '/providers/microsoft.azurestackhci/clusters/', hciClusterName)\r\n| extend encodedResourceId = replace_string(replace_string(clusterResourceId, '/', '%2F'), ' ', '%20')\r\n| extend PortalLink = strcat('https://portal.azure.com/#view/Microsoft_AzureStackHCI_PortalExtension/SingleInstanceHistoryDetails.ReactView/resourceId/', encodedResourceId, '/updateName~/null/updateRunName~/null/refresh~/false')\r\n| join kind=leftouter (\r\n extensibilityresources\r\n | where type == 'microsoft.azurestackhci/clusters/updates/updateruns'\r\n | extend _rState = tostring(properties.state)\r\n | where _rState in ('Succeeded', 'InProgress')\r\n | extend _rCluster = tostring(split(id, '/')[8])\r\n | extend _rUpdate = tostring(split(id, '/')[10])\r\n | project _rCluster, _rUpdate\r\n | summarize _resolvedFlag = count() by _rCluster, _rUpdate\r\n) on $left.hciClusterName == $right._rCluster, $left.updateName == $right._rUpdate\r\n| where not(state == 'Failed' and isnotnull(_resolvedFlag))\r\n| extend _dedup = iff(state == 'Failed', strcat(hciClusterName, '|', updateName), id)\r\n| summarize arg_max(lastUpdatedTime, *) by _dedup\r\n| project-away _dedup\r\n| extend ClusterLink = strcat('https://portal.azure.com/#@/resource', clusterResourceId, '/overview')\r\n| project ClusterName = hciClusterName, ClusterLink, UpdateName = updateName, PortalLink, State = state, Status = progressStatus, CurrentStep, ErrorMessage = ErrorMessageTruncated, ErrorMessageFull = ErrorMessageDisplay, Duration = durationFormatted, TimeStarted = timeStarted, LastUpdated = lastUpdatedTime\r\n| order by TimeStarted desc", "size": 0, "showAnalytics": true, "showRefreshButton": true, "showExportToExcel": true, "queryType": 1, "resourceType": "microsoft.resourcegraph/resources", "crossComponentResources": [ "{Subscriptions}" ], "gridSettings": { "rowLimit": 2000, "formatters": [ { "columnMatch": "UpdateName", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkColumn": "PortalLink" } }, { "columnMatch": "State", "formatter": 18, "formatOptions": { "thresholdsOptions": "icons", "thresholdsGrid": [ { "operator": "==", "thresholdValue": "Succeeded", "representation": "success", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "Failed", "representation": "4", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "InProgress", "representation": "pending", "text": "{0}{1}" }, { "operator": "Default", "thresholdValue": null, "representation": "unknown", "text": "{0}{1}" } ] } }, { "columnMatch": "ErrorMessage", "formatter": 1, "formatOptions": { "linkTarget": "CellDetails", "linkIsContextBlade": true, "customColumnWidthSetting": "45ch" }, "tooltipFormat": { "tooltip": "Click to view full error details" } }, { "columnMatch": "Status", "formatter": 18, "formatOptions": { "thresholdsOptions": "icons", "thresholdsGrid": [ { "operator": "==", "thresholdValue": "Success", "representation": "success", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "Error", "representation": "4", "text": "{0}{1}" }, { "operator": "==", "thresholdValue": "InProgress", "representation": "pending", "text": "{0}{1}" }, { "operator": "Default", "thresholdValue": null, "representation": "unknown", "text": "{0}{1}" } ] } }, { "columnMatch": "PortalLink", "formatter": 5 }, { "columnMatch": "ErrorMessageFull", "formatter": 5 }, { "columnMatch": "ClusterName", "formatter": 7, "formatOptions": { "linkTarget": "Url", "linkColumn": "ClusterLink" } }, { "columnMatch": "ClusterLink", "formatter": 5 } ], "filter": true, "labelSettings": [ { "columnId": "ClusterName", "label": "Cluster Name" }, { "columnId": "UpdateName", "label": "Update Name" }, { "columnId": "PortalLink", "label": "Details" }, { "columnId": "State", "label": "Update State" }, { "columnId": "Status", "label": "Status" }, { "columnId": "CurrentStep", "label": "Current Step" }, { "columnId": "ErrorMessage", "label": "Error Details" }, { "columnId": "Duration", "label": "Duration" }, { "columnId": "TimeStarted", "label": "Time Started" }, { "columnId": "LastUpdated", "label": "Last Updated" } ], "rowDetails": { "type": 1, "title": "Error Details", "content": { "json": "### πŸ”΄ Error Details for {ClusterName}\r\n\r\n| Property | Value |\r\n|:--|:--|\r\n| **Update** | {UpdateName} |\r\n| **State** | {State} |\r\n| **Status** | {Status} |\r\n| **Current Step** | {CurrentStep} |\r\n\r\n---\r\n\r\n#### Full Error Message\r\n\r\n
\r\n\r\n{ErrorMessageFull}\r\n\r\n
\r\n\r\n---\r\n\r\nπŸ’‘ **Tip:** Use the \"View in Portal\" link to see detailed step-by-step progress and additional diagnostics." }, "conditionalVisibility": { "parameterName": "ErrorMessage", "comparison": "isNotEqualTo", "value": "" } } } }, "name": "update-run-history" } ] }, "conditionalVisibility": { "parameterName": "selectedTab", "comparison": "isEqualTo", "value": "5" }, "name": "grp-update-progress" } ], "fallbackResourceIds": [ "azure monitor" ], "$schema": "https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json" }