private ContractsAlert CreatePresentation(Alert alert, bool nullQueryRunInfo = false) { QueryRunInfo queryRunInfo = null; if (!nullQueryRunInfo) { queryRunInfo = new QueryRunInfo { Type = alert.ResourceIdentifier.ResourceType == ResourceType.ApplicationInsights ? TelemetryDbType.ApplicationInsights : TelemetryDbType.LogAnalytics, ResourceIds = new List <string>() { "resourceId1", "resourceId2" } }; } string resourceId = "resourceId"; var request = new SmartDetectorExecutionRequest { ResourceIds = new List <string>() { resourceId }, SmartDetectorId = "smartDetectorId", DataEndTime = DateTime.UtcNow.AddMinutes(-20), Cadence = TimeSpan.FromDays(1), }; return(alert.CreateContractsAlert(request, SmartDetectorName, queryRunInfo)); }
private static ContractsAlert CreateContractsAlert(Alert alert, bool nullQueryRunInfo = false, bool usedLogAnalysisClient = false, bool usedMetricClient = false) { QueryRunInfo queryRunInfo = null; if (!nullQueryRunInfo) { queryRunInfo = new QueryRunInfo { Type = alert.ResourceIdentifier.ResourceType == ResourceType.ApplicationInsights ? TelemetryDbType.ApplicationInsights : TelemetryDbType.LogAnalytics, ResourceIds = new List <string>() { "resourceId1", "resourceId2" } }; } string resourceId = "resourceId"; var request = new SmartDetectorExecutionRequest { ResourceIds = new List <string>() { resourceId }, SmartDetectorId = "smartDetectorId", Cadence = TimeSpan.FromDays(1), }; return(alert.CreateContractsAlert(request, SmartDetectorName, queryRunInfo, usedLogAnalysisClient, usedMetricClient)); }
/// <summary> /// Creating a new instance of <see cref="EmulationAlert"/> for unit tests. /// </summary> /// <param name="alert">The alert to wrap</param> /// <returns>An emulation alert</returns> public static EmulationAlert CreateEmulationAlert(Alert alert) { string resourceId = alert.ResourceIdentifier.ResourceType == ResourceType.ApplicationInsights ? appInsightsResourceId : virtualMachineResourceId; QueryRunInfo queryRunInfo = new QueryRunInfo { Type = alert.ResourceIdentifier.ResourceType == ResourceType.ApplicationInsights ? TelemetryDbType.ApplicationInsights : TelemetryDbType.LogAnalytics, ResourceIds = new List <string>() { resourceId } }; var request = new SmartDetectorExecutionRequest { ResourceIds = new List <string>() { resourceId }, SmartDetectorId = "smartDetectorId", Cadence = TimeSpan.FromDays(1), }; ContractsAlert contractsAlert = alert.CreateContractsAlert(request, "smartDetectorName", queryRunInfo, usedLogAnalysisClient: false, usedMetricClient: false); return(new EmulationAlert(contractsAlert, ExtendedDateTime.UtcNow)); }
/// <summary> /// Creates an instance of <see cref="ITelemetryDataClient"/>, used for running queries against data in log analytics workspaces. /// </summary> /// <param name="resources">The list of resources to analyze.</param> /// <param name="cancellationToken">The cancellation token</param> /// <exception cref="TelemetryDataClientCreationException">A log analytics telemetry data client could not be created for the specified resources.</exception> /// <returns>The telemetry data client, that can be used to run queries on log analytics workspaces.</returns> public async Task <ITelemetryDataClient> CreateLogAnalyticsTelemetryDataClientAsync(IReadOnlyList <ResourceIdentifier> resources, CancellationToken cancellationToken) { // Mark that a log signal was used to create the alert this.UsedLogAnalysisClient = true; // Get the query run info, and verify it QueryRunInfo runInfo = await this.queryRunInfoProvider.GetQueryRunInfoAsync(resources, cancellationToken); VerifyRunInfo(runInfo, TelemetryDbType.LogAnalytics); // Create the client return(new LogAnalyticsTelemetryDataClient(this.tracer, this.httpClientWrapper, this.credentialsFactory, this.azureResourceManagerClient, runInfo.ResourceIds, this.queryTimeout)); }
/// <summary> /// Creates an instance of <see cref="ITelemetryDataClient"/>, used for running queries against data in Application Insights. /// </summary> /// <param name="resources">The list of resources to analyze.</param> /// <param name="cancellationToken">The cancellation token</param> /// <exception cref="TelemetryDataClientCreationException">An Application Insights telemetry data client could not be created for the specified resources.</exception> /// <returns>The telemetry data client, that can be used to run queries on Application Insights.</returns> public async Task <ITelemetryDataClient> CreateApplicationInsightsTelemetryDataClientAsync(IReadOnlyList <ResourceIdentifier> resources, CancellationToken cancellationToken) { // Get the query run info, and verify it QueryRunInfo runInfo = await this.queryRunInfoProvider.GetQueryRunInfoAsync(resources, cancellationToken); this.VerifyRunInfo(runInfo, TelemetryDbType.ApplicationInsights); // Get application Id (for the 1st application) ResourceIdentifier firstApplication = ResourceIdentifier.CreateFromResourceId(runInfo.ResourceIds[0]); string firstApplicationId = await this.azureResourceManagerClient.GetApplicationInsightsAppIdAsync(firstApplication, cancellationToken); // Create the client return(new ApplicationInsightsTelemetryDataClient(this.tracer, this.httpClientWrapper, this.credentialsFactory, firstApplicationId, resources.Select(resource => resource.ToResourceId()), this.queryTimeout)); }
/// <summary> /// Creates an instance of <see cref="ITelemetryDataClient"/>, used for running queries against data in log analytics workspaces. /// </summary> /// <param name="resources">The list of resources to analyze.</param> /// <param name="cancellationToken">The cancellation token</param> /// <exception cref="TelemetryDataClientCreationException">A log analytics telemetry data client could not be created for the specified resources.</exception> /// <returns>The telemetry data client, that can be used to run queries on log analytics workspaces.</returns> public async Task <ITelemetryDataClient> CreateLogAnalyticsTelemetryDataClientAsync(IReadOnlyList <ResourceIdentifier> resources, CancellationToken cancellationToken) { // Get the query run info, and verify it QueryRunInfo runInfo = await this.queryRunInfoProvider.GetQueryRunInfoAsync(resources, cancellationToken); this.VerifyRunInfo(runInfo, TelemetryDbType.LogAnalytics); // Get workspace Id (for the 1st workspace) ResourceIdentifier firstWorkspace = ResourceIdentifier.CreateFromResourceId(runInfo.ResourceIds[0]); string firstWorkspaceId = await this.azureResourceManagerClient.GetLogAnalyticsWorkspaceIdAsync(firstWorkspace, cancellationToken); // Create the client return(new LogAnalyticsTelemetryDataClient(this.tracer, this.httpClientWrapper, this.credentialsFactory, firstWorkspaceId, runInfo.ResourceIds, this.queryTimeout)); }
/// <summary> /// Perform basic validations on the specified query run information. /// </summary> /// <param name="runInfo">The query run information</param> /// <param name="expectedType">The expected telemetry DB type</param> private void VerifyRunInfo(QueryRunInfo runInfo, TelemetryDbType expectedType) { // Verify the telemetry DB type if (runInfo.Type != expectedType) { throw new TelemetryDataClientCreationException($"Telemetry client creation failed - telemetry resource type is {runInfo.Type}"); } // Verify that the resource IDs are not empty if (!runInfo.ResourceIds.Any()) { throw new TelemetryDataClientCreationException("Telemetry client creation failed - no resources found"); } }
public async Task WhenCreatingQueryRunInfoForApplicationInsightsResourcesThenTheCorrectInfoIsCreated() { var resources = new List <ResourceIdentifier>() { new ResourceIdentifier(ResourceType.ApplicationInsights, SubscriptionId, ResourceGroupName, ResourceName) }; IQueryRunInfoProvider provider = new QueryRunInfoProvider(this.azureResourceManagerClientMock.Object); QueryRunInfo queryRunInfo = await provider.GetQueryRunInfoAsync(resources, default(CancellationToken)); Assert.IsNotNull(queryRunInfo, "Query run information is null"); Assert.AreEqual(TelemetryDbType.ApplicationInsights, queryRunInfo.Type, "Wrong telemetry DB type"); CollectionAssert.AreEqual(new[] { resources.First().ToResourceId() }, queryRunInfo.ResourceIds.ToArray(), "Wrong resource IDs"); }
public async Task WhenCreatingQueryRunInfoForLogAnalyticsResourcesThenTheCorrectInfoIsCreated() { var resources = new List <ResourceIdentifier>() { new ResourceIdentifier(ResourceType.LogAnalytics, SubscriptionId, ResourceGroupName, WorkspaceNames[0]), new ResourceIdentifier(ResourceType.LogAnalytics, SubscriptionId, ResourceGroupName, WorkspaceNames[1]) }; string[] resourceIds = resources.Select(r => r.ToResourceId()).ToArray(); IQueryRunInfoProvider provider = new QueryRunInfoProvider(this.azureResourceManagerClientMock.Object); QueryRunInfo queryRunInfo = await provider.GetQueryRunInfoAsync(resources, default(CancellationToken)); Assert.IsNotNull(queryRunInfo, "Query run information is null"); Assert.AreEqual(TelemetryDbType.LogAnalytics, queryRunInfo.Type, "Wrong telemetry DB type"); CollectionAssert.AreEqual(resourceIds, queryRunInfo.ResourceIds.ToArray(), "Wrong resource IDs"); }
public async Task WhenCreatingQueryRunInfoForResourcesWithMultipleSubscriptionsThenAllWorkspacesAreReturned() { var resources = new List <ResourceIdentifier>() { new ResourceIdentifier(ResourceType.VirtualMachine, SubscriptionId + "1", ResourceGroupName + "1", ResourceName + "1"), new ResourceIdentifier(ResourceType.VirtualMachine, SubscriptionId + "2", ResourceGroupName + "2", ResourceName + "2") }; var workspaces = new List <ResourceIdentifier>() { new ResourceIdentifier(ResourceType.LogAnalytics, SubscriptionId + "1", ResourceGroupName + "1", ResourceName + "1"), new ResourceIdentifier(ResourceType.LogAnalytics, SubscriptionId + "2", ResourceGroupName + "2", ResourceName + "2"), new ResourceIdentifier(ResourceType.LogAnalytics, SubscriptionId + "2", ResourceGroupName + "2", ResourceName + "3") }; this.azureResourceManagerClientMock .Setup(x => x.GetAllResourcesInSubscriptionAsync(SubscriptionId + "1", It.IsAny <IEnumerable <ResourceType> >(), It.IsAny <CancellationToken>())) .ReturnsAsync(new List <ResourceIdentifier>() { workspaces[0] }); this.azureResourceManagerClientMock .Setup(x => x.GetAllResourcesInSubscriptionAsync(SubscriptionId + "2", It.IsAny <IEnumerable <ResourceType> >(), It.IsAny <CancellationToken>())) .ReturnsAsync(new List <ResourceIdentifier>() { workspaces[1], workspaces[2] }); this.azureResourceManagerClientMock .Setup(x => x.GetLogAnalyticsWorkspaceIdAsync(It.Is <ResourceIdentifier>(identifier => identifier.ResourceType == ResourceType.LogAnalytics), It.IsAny <CancellationToken>())) .ReturnsAsync("workspaceId"); IQueryRunInfoProvider provider = new QueryRunInfoProvider(this.azureResourceManagerClientMock.Object); QueryRunInfo queryRunInfo = await provider.GetQueryRunInfoAsync(resources, default(CancellationToken)); Assert.IsNotNull(queryRunInfo, "Query run information is null"); this.azureResourceManagerClientMock.Verify(x => x.GetAllResourcesInSubscriptionAsync(SubscriptionId + "1", It.IsAny <IEnumerable <ResourceType> >(), It.IsAny <CancellationToken>()), Times.Once); this.azureResourceManagerClientMock.Verify(x => x.GetAllResourcesInSubscriptionAsync(SubscriptionId + "2", It.IsAny <IEnumerable <ResourceType> >(), It.IsAny <CancellationToken>()), Times.Once); CollectionAssert.AreEqual(workspaces.Select(w => w.ToResourceId()).ToArray(), queryRunInfo.ResourceIds.ToArray()); }
/// <summary> /// Loads the Smart Detector, runs it, and returns the generated alert presentations /// </summary> /// <param name="request">The Smart Detector request</param> /// <param name="cancellationToken">The cancellation token</param> /// <returns>A <see cref="Task{TResult}"/>, returning the list of Alerts presentations generated by the Smart Detector</returns> public async Task <List <ContractsAlert> > RunAsync(SmartDetectorExecutionRequest request, CancellationToken cancellationToken) { // Read the Smart Detector's package this.tracer.TraceInformation($"Loading Smart Detector package for Smart Detector ID {request.SmartDetectorId}"); SmartDetectorPackage smartDetectorPackage = await this.smartDetectorRepository.ReadSmartDetectorPackageAsync(request.SmartDetectorId, cancellationToken); SmartDetectorManifest smartDetectorManifest = smartDetectorPackage.Manifest; this.tracer.TraceInformation($"Read Smart Detector package, ID {smartDetectorManifest.Id}, Version {smartDetectorManifest.Version}"); // Load the Smart Detector ISmartDetector smartDetector = this.smartDetectorLoader.LoadSmartDetector(smartDetectorPackage); this.tracer.TraceInformation($"Smart Detector instance loaded successfully, ID {smartDetectorManifest.Id}"); // Get the resources on which to run the Smart Detector List <ResourceIdentifier> resources = await this.GetResourcesForSmartDetector(request.ResourceIds, smartDetectorManifest, cancellationToken); // Create state repository IStateRepository stateRepository = this.stateRepositoryFactory.Create(request.SmartDetectorId); // Run the Smart Detector this.tracer.TraceInformation($"Started running Smart Detector ID {smartDetectorManifest.Id}, Name {smartDetectorManifest.Name}"); List <Alert> alerts; try { var analysisRequest = new AnalysisRequest(resources, request.DataEndTime, request.Cadence, request.AlertRuleResourceId, this.analysisServicesFactory, stateRepository); alerts = await smartDetector.AnalyzeResourcesAsync(analysisRequest, this.tracer, cancellationToken); this.tracer.TraceInformation($"Completed running Smart Detector ID {smartDetectorManifest.Id}, Name {smartDetectorManifest.Name}, returning {alerts.Count} alerts"); } catch (Exception e) { this.tracer.TraceInformation($"Failed running Smart Detector ID {smartDetectorManifest.Id}, Name {smartDetectorManifest.Name}: {e.Message}"); throw new SmartDetectorCustomException(e.GetType().ToString(), e.Message, e.StackTrace); } // Verify that each alert belongs to one of the types declared in the Smart Detector manifest foreach (Alert alert in alerts) { if (!smartDetectorManifest.SupportedResourceTypes.Contains(alert.ResourceIdentifier.ResourceType)) { throw new UnidentifiedAlertResourceTypeException(alert.ResourceIdentifier); } } // Trace the number of alerts of each type foreach (var alertType in alerts.GroupBy(x => x.GetType().Name)) { this.tracer.TraceInformation($"Got {alertType.Count()} Alerts of type '{alertType.Key}'"); this.tracer.ReportMetric("AlertType", alertType.Count(), new Dictionary <string, string>() { { "AlertType", alertType.Key } }); } // Create results List <ContractsAlert> results = new List <ContractsAlert>(); foreach (var alert in alerts) { QueryRunInfo queryRunInfo = await this.queryRunInfoProvider.GetQueryRunInfoAsync(new List <ResourceIdentifier>() { alert.ResourceIdentifier }, cancellationToken); results.Add(alert.CreateContractsAlert(request, smartDetectorManifest.Name, queryRunInfo)); } this.tracer.TraceInformation($"Returning {results.Count} results"); return(results); }
/// <summary> /// Creates a presentation from a alert /// </summary> /// <param name="alert">The alert</param> /// <param name="request">The Smart Detector request</param> /// <param name="smartDetectorName">The Smart Detector name</param> /// <param name="queryRunInfo">The query run information</param> /// <returns>The presentation</returns> public static ContractsAlert CreateContractsAlert(this Alert alert, SmartDetectorExecutionRequest request, string smartDetectorName, QueryRunInfo queryRunInfo) { // A null alert has null presentation if (alert == null) { return(null); } // Create presentation elements for each alert property Dictionary <string, string> predicates = new Dictionary <string, string>(); List <AlertProperty> alertProperties = new List <AlertProperty>(); Dictionary <string, string> rawProperties = new Dictionary <string, string>(); foreach (PropertyInfo property in alert.GetType().GetProperties()) { // Get the property value string propertyValue = PropertyValueToString(property.GetValue(alert)); if (string.IsNullOrWhiteSpace(propertyValue)) { // not accepting empty properties continue; } rawProperties[property.Name] = propertyValue; // Check if this property is a predicate if (property.GetCustomAttribute <AlertPredicatePropertyAttribute>() != null) { predicates[property.Name] = propertyValue; } // Get the presentation attribute AlertPresentationPropertyAttribute attribute = property.GetCustomAttribute <AlertPresentationPropertyAttribute>(); if (attribute != null) { // Verify that if the entity is a chart or query, then query run information was provided if (queryRunInfo == null && (attribute.Section == AlertPresentationSection.Chart || attribute.Section == AlertPresentationSection.AdditionalQuery)) { throw new InvalidAlertPresentationException($"The presentation contains an item for the {attribute.Section} section, but no telemetry data client was provided"); } // Get the attribute title and information balloon - support interpolated strings string attributeTitle = Smart.Format(attribute.Title, alert); string attributeInfoBalloon = Smart.Format(attribute.InfoBalloon, alert); // Add the presentation property alertProperties.Add(new AlertProperty { Name = attributeTitle, Value = propertyValue, DisplayCategory = GetDisplayCategoryFromPresentationSection(attribute.Section), InfoBalloon = attributeInfoBalloon }); } } string id = string.Join("##", alert.GetType().FullName, JsonConvert.SerializeObject(request), JsonConvert.SerializeObject(alert)).Hash(); string resourceId = alert.ResourceIdentifier.ToResourceId(); string correlationHash = string.Join("##", predicates.OrderBy(x => x.Key).Select(x => x.Key + "|" + x.Value)).Hash(); // Return the presentation object return(new ContractsAlert { Id = id, Title = alert.Title, ResourceId = resourceId, CorrelationHash = correlationHash, SmartDetectorId = request.SmartDetectorId, SmartDetectorName = smartDetectorName, AnalysisTimestamp = DateTime.UtcNow, AnalysisWindowSizeInMinutes = (int)request.Cadence.TotalMinutes, Properties = alertProperties, RawProperties = rawProperties, QueryRunInfo = queryRunInfo }); }
/// <summary> /// Creates a presentation from an alert /// </summary> /// <param name="alert">The alert</param> /// <param name="request">The Smart Detector request</param> /// <param name="smartDetectorName">The Smart Detector name</param> /// <param name="queryRunInfo">The query run information</param> /// <param name="usedLogAnalysisClient">Indicates whether a log analysis client was used to create the alert</param> /// <param name="usedMetricClient">Indicates whether a metric client was used to create the alert</param> /// <returns>The presentation</returns> public static ContractsAlert CreateContractsAlert(this Alert alert, SmartDetectorExecutionRequest request, string smartDetectorName, QueryRunInfo queryRunInfo, bool usedLogAnalysisClient, bool usedMetricClient) { // A null alert has null presentation if (alert == null) { return(null); } // Create presentation elements for each alert property Dictionary <string, string> predicates = new Dictionary <string, string>(); #pragma warning disable CS0612 // Type or member is obsolete; Task to remove obsolete code #1312924 List <AlertPropertyLegacy> alertPropertiesLegacy = new List <AlertPropertyLegacy>(); #pragma warning restore CS0612 // Type or member is obsolete; Task to remove obsolete code #1312924 List <AlertProperty> alertProperties = new List <AlertProperty>(); Dictionary <string, string> rawProperties = new Dictionary <string, string>(); List <string> alertBaseClassPropertiesNames = typeof(Alert).GetProperties().Select(p => p.Name).ToList(); foreach (PropertyInfo property in alert.GetType().GetProperties()) { // Get the property value object propertyValue = property.GetValue(alert); string propertyStringValue = PropertyValueToString(propertyValue); if (string.IsNullOrWhiteSpace(propertyStringValue) || (propertyValue is ICollection value && value.Count == 0)) { // not accepting empty properties continue; } rawProperties[property.Name] = propertyStringValue; // Check if this property is a predicate if (property.GetCustomAttribute <AlertPredicatePropertyAttribute>() != null) { predicates[property.Name] = propertyStringValue; } // Get the v1 presentation attribute AlertPresentationPropertyAttribute presentationAttribute = property.GetCustomAttribute <AlertPresentationPropertyAttribute>(); if (presentationAttribute != null) { alertPropertiesLegacy.Add(CreateAlertPropertyLegacy(alert, presentationAttribute, queryRunInfo, propertyStringValue)); } // Get the v2 presentation attribute AlertPresentationPropertyV2Attribute presentationV2Attribute = property.GetCustomAttribute <AlertPresentationPropertyV2Attribute>(); if (presentationV2Attribute != null) { alertProperties.Add(CreateAlertProperty(alert, presentationV2Attribute, property.Name, propertyValue)); } else if (!alertBaseClassPropertiesNames.Contains(property.Name)) { // Get the raw alert property - a property with no presentation alertProperties.Add(new RawAlertProperty(property.Name, propertyValue)); } } string id = string.Join("##", alert.GetType().FullName, JsonConvert.SerializeObject(request), JsonConvert.SerializeObject(alert)).Hash(); string resourceId = alert.ResourceIdentifier.ToResourceId(); string correlationHash = string.Join("##", predicates.OrderBy(x => x.Key).Select(x => x.Key + "|" + x.Value)).Hash(); // Get the alert's signal type based on the clients used to create the alert SignalType signalType = GetSignalType(usedLogAnalysisClient, usedMetricClient); // Return the presentation object #pragma warning disable CS0612 // Type or member is obsolete; Task to remove obsolete code #1312924 return(new ContractsAlert { Id = id, State = (alert.State == AlertState.Active) ? ContractsAlertState.Active : ContractsAlertState.Resolved, Title = alert.Title, ResourceId = resourceId, CorrelationHash = correlationHash, SmartDetectorId = request.SmartDetectorId, SmartDetectorName = smartDetectorName, AnalysisTimestamp = DateTime.UtcNow, AnalysisWindowSizeInMinutes = (int)request.Cadence.TotalMinutes, Properties = alertPropertiesLegacy, AlertProperties = alertProperties, RawProperties = rawProperties, QueryRunInfo = queryRunInfo, SignalType = signalType }); #pragma warning restore CS0612 // Type or member is obsolete; Task to remove obsolete code #1312924 }
/// <summary> /// Creates an <see cref="AlertPropertyLegacy"/> based on an alert presentation V1 property /// </summary> /// <param name="alert">The alert</param> /// <param name="presentationAttribute">The attribute defining the presentation V1 of the alert property</param> /// <param name="queryRunInfo">The query run information</param> /// <param name="propertyStringValue">The property string value</param> /// <returns>An <see cref="AlertPropertyLegacy"/></returns> #pragma warning disable CS0612 // Type or member is obsolete; Task to remove obsolete code #1312924 private static AlertPropertyLegacy CreateAlertPropertyLegacy(Alert alert, AlertPresentationPropertyAttribute presentationAttribute, QueryRunInfo queryRunInfo, string propertyStringValue) { // Verify that if the entity is a chart or query, then query run information was provided if (queryRunInfo == null && (presentationAttribute.Section == AlertPresentationSection.Chart || presentationAttribute.Section == AlertPresentationSection.AdditionalQuery)) { throw new InvalidAlertPresentationException($"The presentation contains an item for the {presentationAttribute.Section} section, but no telemetry data client was provided"); } // Get the attribute title and information balloon - support interpolated strings string attributeTitle = StringExtensions.EvaluateInterpolatedString(presentationAttribute.Title, alert); string attributeInfoBalloon = StringExtensions.EvaluateInterpolatedString(presentationAttribute.InfoBalloon, alert); // Add the presentation property return(new AlertPropertyLegacy() { Name = attributeTitle, Value = propertyStringValue, DisplayCategory = GetDisplayCategoryFromPresentationSection(presentationAttribute.Section), InfoBalloon = attributeInfoBalloon, Order = presentationAttribute.Order }); }
/// <summary> /// Runs the Smart Detector. /// </summary> /// <param name="resources">The resources which the Smart Detector should run on</param> /// <param name="analysisCadence">The analysis cadence</param> /// <param name="startTimeRange">The start time</param> /// <param name="endTimeRange">The end time</param> /// <returns>A task that runs the Smart Detector</returns> public async Task RunAsync(List <ResourceIdentifier> resources, TimeSpan analysisCadence, DateTime startTimeRange, DateTime endTimeRange) { this.cancellationTokenSource = new CancellationTokenSource(); IStateRepository stateRepository = this.stateRepositoryFactory.Create(this.smartDetectorId); List <string> resourceIds = resources.Select(resource => resource.ResourceName).ToList(); this.Alerts.Clear(); try { // Run Smart Detector this.IsSmartDetectorRunning = true; int totalRunsAmount = (int)((endTimeRange.Subtract(startTimeRange).Ticks / analysisCadence.Ticks) + 1); int currentRunNumber = 1; for (var currentTime = startTimeRange; currentTime <= endTimeRange; currentTime = currentTime.Add(analysisCadence)) { this.tracer.TraceInformation($"Start analysis, end of time range: {currentTime}"); var analysisRequest = new AnalysisRequest(resources, currentTime, analysisCadence, null, this.analysisServicesFactory, stateRepository); var newAlerts = await this.smartDetector.AnalyzeResourcesAsync( analysisRequest, this.Tracer, this.cancellationTokenSource.Token); var smartDetectorExecutionRequest = new SmartDetectorExecutionRequest { ResourceIds = resourceIds, SmartDetectorId = this.smartDetectorManifes.Id, Cadence = analysisCadence, DataEndTime = currentTime }; // Show the alerts that were found in this iteration newAlerts.ForEach(async newAlert => { QueryRunInfo queryRunInfo = await this.queryRunInfoProvider.GetQueryRunInfoAsync(new List <ResourceIdentifier>() { newAlert.ResourceIdentifier }, this.cancellationTokenSource.Token); ContractsAlert contractsAlert = newAlert.CreateContractsAlert(smartDetectorExecutionRequest, this.smartDetectorManifes.Name, queryRunInfo); // Create Azure resource identifier ResourceIdentifier resourceIdentifier = ResourceIdentifier.CreateFromResourceId(contractsAlert.ResourceId); this.Alerts.Add(new EmulationAlert(contractsAlert, resourceIdentifier, currentTime)); }); this.tracer.TraceInformation($"completed {currentRunNumber} of {totalRunsAmount} runs"); currentRunNumber++; } string separator = "====================================================================================================="; this.tracer.TraceInformation($"Total alerts found: {this.Alerts.Count} {Environment.NewLine} {separator}"); } catch (OperationCanceledException) { this.Tracer.TraceError("Smart Detector run was canceled."); } catch (Exception e) { this.Tracer.ReportException(e); } finally { this.IsSmartDetectorRunning = false; this.cancellationTokenSource?.Dispose(); } }