From 92ebf85ff112ef70898af310f682f8b84fea57ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20=C3=96hlund?= Date: Tue, 9 Dec 2025 20:44:00 +0100 Subject: [PATCH 1/7] Check result from azure monitor better --- src/Query/AzureServiceBus/AzureClient.cs | 46 +++++++++++++++++++----- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/src/Query/AzureServiceBus/AzureClient.cs b/src/Query/AzureServiceBus/AzureClient.cs index 501d9a8b..4cdcbf01 100644 --- a/src/Query/AzureServiceBus/AzureClient.cs +++ b/src/Query/AzureServiceBus/AzureClient.cs @@ -20,13 +20,16 @@ public class AzureClient Queue connectionQueue; AuthenticatedClientSet currentClients; + const string CompleteMessageMetricName = "CompleteMessage"; + const string MicrosoftServicebusNamespacesMetricsNamespace = "Microsoft.ServiceBus/Namespaces"; public string FullyQualifiedNamespace { get; } + public AzureClient(string resourceId, string serviceBusDomain, string region, string metricsDomain, Action log = null) { this.resourceId = ResourceIdentifier.Parse(resourceId); - this.log = log ?? new(msg => { }); + this.log = log ?? (_ => { }); FullyQualifiedNamespace = $"{this.resourceId.Name}.{serviceBusDomain}"; @@ -43,9 +46,14 @@ IEnumerable CreateCredentials() yield return new VisualStudioCredential(); // Don't really need this one to take 100s * 4 tries to finally time out - var opts = new TokenCredentialOptions(); - opts.Retry.MaxRetries = 1; - opts.Retry.NetworkTimeout = TimeSpan.FromSeconds(10); + var opts = new TokenCredentialOptions + { + Retry = + { + MaxRetries = 1, + NetworkTimeout = TimeSpan.FromSeconds(10) + } + }; yield return new ManagedIdentityCredential(FullyQualifiedNamespace, opts); } @@ -108,8 +116,8 @@ public Task> GetMetrics(string queueName, DateOnly startTime, { var response = await currentClients.Metrics.QueryResourcesAsync( [resourceId], - ["CompleteMessage"], - "Microsoft.ServiceBus/Namespaces", + [CompleteMessageMetricName], + MicrosoftServicebusNamespacesMetricsNamespace, new MetricsQueryResourcesOptions { Filter = $"EntityName eq '{queueName}'", @@ -119,8 +127,28 @@ public Task> GetMetrics(string queueName, DateOnly startTime, }, token).ConfigureAwait(false); - // Yeah, it's buried deep - return response.Value.Values.FirstOrDefault()?.Metrics.FirstOrDefault()?.TimeSeries.FirstOrDefault()?.Values ?? []; + var metricQueryResult = response.Value.Values.SingleOrDefault(mr => mr.Namespace == MicrosoftServicebusNamespacesMetricsNamespace); + + if (metricQueryResult is null) + { + throw new Exception("No metrics query results returned for Microsoft.ServiceBus/Namespace"); + } + + var metricResult = metricQueryResult.GetMetricByName(CompleteMessageMetricName); + + if (metricResult.Error.Message is not null) + { + throw new Exception($"Metrics query result for '{metricResult.Name}' failed: {metricResult.Error.Message}"); + } + + var timeSeries = metricResult.TimeSeries.SingleOrDefault(); + + if (timeSeries is null) + { + throw new Exception($"Metrics query result for '{metricResult.Name}' contained no time series"); + } + + return timeSeries.Values; } catch (Azure.RequestFailedException reqFailed) when (reqFailed.Message.Contains("ResourceGroupNotFound")) { @@ -136,7 +164,7 @@ public Task GetQueueNames(CancellationToken cancellationToken = defaul return GetDataWithCurrentCredentials(async token => { var queueList = new List(); - await foreach (var queue in currentClients.ServiceBus.GetQueuesAsync(cancellationToken).WithCancellation(cancellationToken)) + await foreach (var queue in currentClients.ServiceBus.GetQueuesAsync(token)) { queueList.Add(queue.Name); } From 2ba0fe7f8940e9d4399e0b2f462676037818d617 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20=C3=96hlund?= Date: Tue, 9 Dec 2025 20:44:44 +0100 Subject: [PATCH 2/7] Better output for azure service bus results --- .../Commands/AzureServiceBusCommand.cs | 81 +++++++++---------- 1 file changed, 40 insertions(+), 41 deletions(-) diff --git a/src/AppCommon/Commands/AzureServiceBusCommand.cs b/src/AppCommon/Commands/AzureServiceBusCommand.cs index 2ce7d8f2..e8c3e4a1 100644 --- a/src/AppCommon/Commands/AzureServiceBusCommand.cs +++ b/src/AppCommon/Commands/AzureServiceBusCommand.cs @@ -12,29 +12,17 @@ public static Command CreateCommand() var resourceIdArg = new Option( name: "--resourceId", - description: "The resource id for the Azure Service Bus namespace, which can be found in the Properties page in the Azure Portal.") - { - IsRequired = true - }; + description: "The resource id for the Azure Service Bus namespace, which can be found in the Properties page in the Azure Portal.") { IsRequired = true }; var serviceBusDomainArg = new Option("--serviceBusDomain", - description: "The Service Bus domain. Defaults to 'servicebus.windows.net' and only must be specified for Azure customers using non-standard domains like government cloud customers.") - { - IsRequired = false - }; + description: "The Service Bus domain. Defaults to 'servicebus.windows.net' and only must be specified for Azure customers using non-standard domains like government cloud customers.") { IsRequired = false }; var regionArg = new Option( name: "--region", - description: "The Azure region where the Service Bus namespace is located, which is listed as the location in the Properties page in the Azure Portal.") - { - IsRequired = true - }; + description: "The Azure region where the Service Bus namespace is located, which is listed as the location in the Properties page in the Azure Portal.") { IsRequired = true }; var metricsDomainArg = new Option("--metricsDomain", - description: "The Azure Monitor Metrics domain. Defaults to 'metrics.monitor.azure.com' and only must be specified for Azure customers using non-standard domains like government cloud customers.") - { - IsRequired = false - }; + description: "The Azure Monitor Metrics domain. Defaults to 'metrics.monitor.azure.com' and only must be specified for Azure customers using non-standard domains like government cloud customers.") { IsRequired = false }; serviceBusDomainArg.SetDefaultValue("servicebus.windows.net"); metricsDomainArg.SetDefaultValue("metrics.monitor.azure.com"); @@ -74,7 +62,7 @@ public static Command CreateCommand() string[] queueNames; public AzureServiceBusCommand(SharedOptions shared, string resourceId, string serviceBusDomain, string region, string metricsDomain) - : base(shared) + : base(shared) { azure = new AzureClient(resourceId, serviceBusDomain, region, metricsDomain, Out.WriteLine); RunInfo.Add("AzureServiceBusNamespace", azure.FullyQualifiedNamespace); @@ -84,7 +72,7 @@ protected override async Task GetData(CancellationToken cancellati { try { - var endTime = DateOnly.FromDateTime(DateTime.UtcNow).AddDays(-1); + var endTime = DateOnly.FromDateTime(DateTime.UtcNow); var startTime = endTime.AddDays(-90); var results = new List(); @@ -95,40 +83,51 @@ protected override async Task GetData(CancellationToken cancellati { var queueName = queueNames[i]; - Out.WriteLine($"Gathering metrics for queue {i + 1}/{queueNames.Length}: {queueName}"); + Out.Write($"Gathering metrics for queue {i + 1}/{queueNames.Length}: {queueName}"); var metricValues = (await azure.GetMetrics(queueName, startTime, endTime, cancellationToken)).OrderBy(m => m.TimeStamp).ToArray(); - if (metricValues is not null) - { - var maxThroughput = metricValues.Select(timeEntry => timeEntry.Total).Max(); + var maxThroughput = metricValues.Select(timeEntry => timeEntry.Total).Max(); - // Since we get 90 days of data, if there's no throughput in that amount of time, hard to legitimately call it an endpoint - if (maxThroughput is not null and not 0) + // Since we get 90 days of data, if there's no throughput in that amount of time, hard to legitimately call it an endpoint + if (maxThroughput is not null and not 0) + { + var start = DateOnly.FromDateTime(metricValues.First().TimeStamp.UtcDateTime); + var end = DateOnly.FromDateTime(metricValues.Last().TimeStamp.UtcDateTime); + var currentDate = start; + var data = new Dictionary(); + while (currentDate <= end) { - var start = DateOnly.FromDateTime(metricValues.First().TimeStamp.UtcDateTime); - var end = DateOnly.FromDateTime(metricValues.Last().TimeStamp.UtcDateTime); - var currentDate = start; - var data = new Dictionary(); - while (currentDate <= end) + data.Add(currentDate, new DailyThroughput { - data.Add(currentDate, new DailyThroughput { MessageCount = 0, DateUTC = currentDate }); + MessageCount = 0, + DateUTC = currentDate + }); - currentDate = currentDate.AddDays(1); - } + currentDate = currentDate.AddDays(1); + } - foreach (var metricValue in metricValues) + foreach (var metricValue in metricValues) + { + currentDate = DateOnly.FromDateTime(metricValue.TimeStamp.UtcDateTime); + data[currentDate] = new DailyThroughput { - currentDate = DateOnly.FromDateTime(metricValue.TimeStamp.UtcDateTime); - data[currentDate] = new DailyThroughput { MessageCount = (long)(metricValue.Total ?? 0), DateUTC = currentDate }; - } - - results.Add(new QueueThroughput { QueueName = queueName, Throughput = (long?)maxThroughput, DailyThroughputFromBroker = [.. data.Values] }); + MessageCount = (long)(metricValue.Total ?? 0), + DateUTC = currentDate + }; } - else + + results.Add(new QueueThroughput { - Out.WriteLine(" - No throughput detected in 90 days, ignoring"); - } + QueueName = queueName, + Throughput = (long?)maxThroughput, + DailyThroughputFromBroker = [.. data.Values] + }); + Out.WriteLine($" - MaxThroughput: {maxThroughput}"); + } + else + { + Out.WriteLine(" - No throughput detected in 90 days, ignoring"); } } From ac2bfc618f71a0099dbc337472bfdb524dd88fc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20=C3=96hlund?= Date: Tue, 9 Dec 2025 20:53:23 +0100 Subject: [PATCH 3/7] Formatting --- .../Commands/AzureServiceBusCommand.cs | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/AppCommon/Commands/AzureServiceBusCommand.cs b/src/AppCommon/Commands/AzureServiceBusCommand.cs index e8c3e4a1..ba0bd0f0 100644 --- a/src/AppCommon/Commands/AzureServiceBusCommand.cs +++ b/src/AppCommon/Commands/AzureServiceBusCommand.cs @@ -12,17 +12,29 @@ public static Command CreateCommand() var resourceIdArg = new Option( name: "--resourceId", - description: "The resource id for the Azure Service Bus namespace, which can be found in the Properties page in the Azure Portal.") { IsRequired = true }; + description: "The resource id for the Azure Service Bus namespace, which can be found in the Properties page in the Azure Portal.") + { + IsRequired = true + }; var serviceBusDomainArg = new Option("--serviceBusDomain", - description: "The Service Bus domain. Defaults to 'servicebus.windows.net' and only must be specified for Azure customers using non-standard domains like government cloud customers.") { IsRequired = false }; + description: "The Service Bus domain. Defaults to 'servicebus.windows.net' and only must be specified for Azure customers using non-standard domains like government cloud customers.") + { + IsRequired = false + }; var regionArg = new Option( name: "--region", - description: "The Azure region where the Service Bus namespace is located, which is listed as the location in the Properties page in the Azure Portal.") { IsRequired = true }; + description: "The Azure region where the Service Bus namespace is located, which is listed as the location in the Properties page in the Azure Portal.") + { + IsRequired = true + }; var metricsDomainArg = new Option("--metricsDomain", - description: "The Azure Monitor Metrics domain. Defaults to 'metrics.monitor.azure.com' and only must be specified for Azure customers using non-standard domains like government cloud customers.") { IsRequired = false }; + description: "The Azure Monitor Metrics domain. Defaults to 'metrics.monitor.azure.com' and only must be specified for Azure customers using non-standard domains like government cloud customers.") + { + IsRequired = false + }; serviceBusDomainArg.SetDefaultValue("servicebus.windows.net"); metricsDomainArg.SetDefaultValue("metrics.monitor.azure.com"); From 5fcd3562ba4a8ded07479c27a48c6ce71478c37a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20=C3=96hlund?= Date: Tue, 9 Dec 2025 20:57:51 +0100 Subject: [PATCH 4/7] Better output --- src/AppCommon/Commands/AzureServiceBusCommand.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AppCommon/Commands/AzureServiceBusCommand.cs b/src/AppCommon/Commands/AzureServiceBusCommand.cs index ba0bd0f0..c71d35aa 100644 --- a/src/AppCommon/Commands/AzureServiceBusCommand.cs +++ b/src/AppCommon/Commands/AzureServiceBusCommand.cs @@ -135,7 +135,7 @@ protected override async Task GetData(CancellationToken cancellati Throughput = (long?)maxThroughput, DailyThroughputFromBroker = [.. data.Values] }); - Out.WriteLine($" - MaxThroughput: {maxThroughput}"); + Out.WriteLine($" - Max daily throughput: {maxThroughput}"); } else { From c8c7d2424ffff859c8b254869bd1a65e47014edb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20=C3=96hlund?= Date: Tue, 9 Dec 2025 21:01:11 +0100 Subject: [PATCH 5/7] Include dates --- src/AppCommon/Commands/AzureServiceBusCommand.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AppCommon/Commands/AzureServiceBusCommand.cs b/src/AppCommon/Commands/AzureServiceBusCommand.cs index c71d35aa..354e27c8 100644 --- a/src/AppCommon/Commands/AzureServiceBusCommand.cs +++ b/src/AppCommon/Commands/AzureServiceBusCommand.cs @@ -135,7 +135,7 @@ protected override async Task GetData(CancellationToken cancellati Throughput = (long?)maxThroughput, DailyThroughputFromBroker = [.. data.Values] }); - Out.WriteLine($" - Max daily throughput: {maxThroughput}"); + Out.WriteLine($" - Max daily throughput: {maxThroughput} ({start.ToShortDateString()} - {end.ToShortDateString()})"); } else { From d83c8e2436494b1892b88afd5759d1bf02bf42e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20=C3=96hlund?= Date: Wed, 10 Dec 2025 09:03:11 +0100 Subject: [PATCH 6/7] Output dates for not found --- src/AppCommon/Commands/AzureServiceBusCommand.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/AppCommon/Commands/AzureServiceBusCommand.cs b/src/AppCommon/Commands/AzureServiceBusCommand.cs index 354e27c8..d0c57bf0 100644 --- a/src/AppCommon/Commands/AzureServiceBusCommand.cs +++ b/src/AppCommon/Commands/AzureServiceBusCommand.cs @@ -85,7 +85,7 @@ protected override async Task GetData(CancellationToken cancellati try { var endTime = DateOnly.FromDateTime(DateTime.UtcNow); - var startTime = endTime.AddDays(-90); + var startTime = endTime.AddDays(-90); // Azure Monitor only gives a data for a month back, but we ask for more just in case var results = new List(); azure.ResetConnectionQueue(); @@ -100,12 +100,12 @@ protected override async Task GetData(CancellationToken cancellati var metricValues = (await azure.GetMetrics(queueName, startTime, endTime, cancellationToken)).OrderBy(m => m.TimeStamp).ToArray(); var maxThroughput = metricValues.Select(timeEntry => timeEntry.Total).Max(); + var start = DateOnly.FromDateTime(metricValues.First().TimeStamp.UtcDateTime); + var end = DateOnly.FromDateTime(metricValues.Last().TimeStamp.UtcDateTime); - // Since we get 90 days of data, if there's no throughput in that amount of time, hard to legitimately call it an endpoint + // If there's no throughput in that amount of time, hard to legitimately call it an endpoint if (maxThroughput is not null and not 0) { - var start = DateOnly.FromDateTime(metricValues.First().TimeStamp.UtcDateTime); - var end = DateOnly.FromDateTime(metricValues.Last().TimeStamp.UtcDateTime); var currentDate = start; var data = new Dictionary(); while (currentDate <= end) @@ -139,7 +139,7 @@ protected override async Task GetData(CancellationToken cancellati } else { - Out.WriteLine(" - No throughput detected in 90 days, ignoring"); + Out.WriteLine($" - No throughput detected in during ({start.ToShortDateString()} - {end.ToShortDateString()}), ignoring"); } } From 43814ad38133c987724216dd0562fff0dedcbc36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20=C3=96hlund?= Date: Wed, 10 Dec 2025 12:38:24 +0100 Subject: [PATCH 7/7] Better message --- src/AppCommon/Commands/AzureServiceBusCommand.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AppCommon/Commands/AzureServiceBusCommand.cs b/src/AppCommon/Commands/AzureServiceBusCommand.cs index d0c57bf0..a1b575dd 100644 --- a/src/AppCommon/Commands/AzureServiceBusCommand.cs +++ b/src/AppCommon/Commands/AzureServiceBusCommand.cs @@ -139,7 +139,7 @@ protected override async Task GetData(CancellationToken cancellati } else { - Out.WriteLine($" - No throughput detected in during ({start.ToShortDateString()} - {end.ToShortDateString()}), ignoring"); + Out.WriteLine($" - No throughput detected for the period {start.ToShortDateString()} - {end.ToShortDateString()}, ignoring"); } }