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");
            }
        }
Beispiel #8
0
        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");
        }
Beispiel #9
0
        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");
        }
Beispiel #10
0
        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);
        }
Beispiel #12
0
        /// <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();
            }
        }