/// <summary> /// Runs the smart signal. /// </summary> /// <param name="resources">The resources which the signal should run on.</param> /// <param name="analysisCadence">The analysis cadence.</param> /// <returns>A task that runs the smart signal.</returns> public async Task RunAsync(List <ResourceIdentifier> resources, TimeSpan analysisCadence) { this.cancellationTokenSource = new CancellationTokenSource(); this.Results.Clear(); var analysisRequest = new AnalysisRequest(resources, null, analysisCadence, this.analysisServicesFactory); try { // Run Signal this.IsSignalRunning = true; SmartSignalResult signalResults = await this.smartSignal.AnalyzeResourcesAsync( analysisRequest, this.Tracer, this.cancellationTokenSource.Token); // Create signal result items List <SignalResultItem> signalResultItems = new List <SignalResultItem>(); foreach (var resultItem in signalResults.ResultItems) { // Create result item presentation var resourceIds = resources.Select(resource => resource.ResourceName).ToList(); var smartSignalsSettings = new SmartSignalSettings(); var smartSignalRequest = new SmartSignalRequest(resourceIds, this.smartSignalManifes.Id, null, analysisCadence, smartSignalsSettings); SmartSignalResultItemQueryRunInfo queryRunInfo = await this.queryRunInfoProvider.GetQueryRunInfoAsync(new List <ResourceIdentifier>() { resultItem.ResourceIdentifier }, this.cancellationTokenSource.Token); SmartSignalResultItemPresentation resultItemPresentation = SmartSignalResultItemPresentation.CreateFromResultItem( smartSignalRequest, this.smartSignalManifes.Name, resultItem, queryRunInfo); // Create Azure resource identifier ResourceIdentifier resourceIdentifier = ResourceIdentifier.CreateFromResourceId(resultItemPresentation.ResourceId); signalResultItems.Add(new SignalResultItem(resultItemPresentation, resourceIdentifier)); } this.Results = new ObservableCollection <SignalResultItem>(signalResultItems); this.tracer.TraceInformation($"Found {this.Results.Count} results"); } catch (OperationCanceledException) { this.Tracer.TraceError("Signal run was canceled."); } catch (Exception e) { this.Tracer.ReportException(e); } finally { this.IsSignalRunning = false; this.cancellationTokenSource?.Dispose(); } }
public static async Task <HttpResponseMessage> RunAsync( [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "analyze")] HttpRequestMessage request, TraceWriter log, ExecutionContext context, CancellationToken cancellationToken) { using (IUnityContainer childContainer = Container.CreateChildContainer().WithTracer(log, true)) { // Create a tracer for this run (that will also log to the specified TraceWriter) ITracer tracer = childContainer.Resolve <ITracer>(); tracer.TraceInformation($"Analyze function request received with invocation Id {context.InvocationId}"); tracer.AddCustomProperty("FunctionName", context.FunctionName); tracer.AddCustomProperty("InvocationId", context.InvocationId.ToString("N")); try { // Trace app counters (before analysis) tracer.TraceAppCounters(); // Read the request SmartSignalRequest smartSignalRequest = await request.Content.ReadAsAsync <SmartSignalRequest>(cancellationToken); tracer.AddCustomProperty("SignalId", smartSignalRequest.SignalId); tracer.TraceInformation($"Analyze request received: {JsonConvert.SerializeObject(smartSignalRequest)}"); // Process the request ISmartSignalRunner runner = childContainer.Resolve <ISmartSignalRunner>(); List <SmartSignalResultItemPresentation> resultPresentations = await runner.RunAsync(smartSignalRequest, cancellationToken); tracer.TraceInformation($"Analyze completed, returning {resultPresentations.Count} results"); // Return the generated presentations return(request.CreateResponse(HttpStatusCode.OK, resultPresentations)); } catch (Exception e) { // Handle the exception TopLevelExceptionHandler.TraceUnhandledException(e, tracer, log); // Return error status return(request.CreateResponse(HttpStatusCode.InternalServerError, e.Message)); } finally { // Trace app counters (after analysis) tracer.TraceAppCounters(); } } }
/// <summary> /// Runs the signal analysis, in a separate process /// </summary> /// <param name="request">The request</param> /// <param name="cancellationToken">The cancellation token</param> /// <returns>A <see cref="Task{TResult}"/>, returning the generated result items presentations</returns> public async Task <List <SmartSignalResultItemPresentation> > RunAsync(SmartSignalRequest request, CancellationToken cancellationToken) { // Find the executable location string currentDllPath = new Uri(typeof(SmartSignalRunnerInChildProcess).Assembly.CodeBase).AbsolutePath; string exePath = Path.Combine(Path.GetDirectoryName(currentDllPath) ?? string.Empty, ChildProcessName); if (!File.Exists(exePath)) { this.tracer.TraceError($"Verification of executable path {exePath} failed"); throw new FileNotFoundException("Could not find child process executable", ChildProcessName); } // Run the child process return(await this.childProcessManager.RunChildProcessAsync <List <SmartSignalResultItemPresentation> >(exePath, request, cancellationToken)); }
private SmartSignalResultItemPresentation CreatePresentation(SmartSignalResultItem resultItem, bool nullQueryRunInfo = false) { SmartSignalResultItemQueryRunInfo queryRunInfo = null; if (!nullQueryRunInfo) { queryRunInfo = new SmartSignalResultItemQueryRunInfo( resultItem.ResourceIdentifier.ResourceType == ResourceType.ApplicationInsights ? TelemetryDbType.ApplicationInsights : TelemetryDbType.LogAnalytics, new List <string>() { "resourceId1", "resourceId2" }); } DateTime lastExecutionTime = DateTime.Now.Date.AddDays(-1); string resourceId = "resourceId"; var request = new SmartSignalRequest(new List <string>() { resourceId }, "signalId", lastExecutionTime, TimeSpan.FromDays(1), new SmartSignalSettings()); return(SmartSignalResultItemPresentation.CreateFromResultItem(request, SignalName, resultItem, queryRunInfo)); }
/// <summary> /// Loads the signal, runs it, and returns the generated result presentations /// </summary> /// <param name="request">The signal request</param> /// <param name="cancellationToken">The cancellation token</param> /// <returns>A <see cref="Task{TResult}"/>, returning the list of Smart Signal result item presentations generated by the signal</returns> public async Task <List <SmartSignalResultItemPresentation> > RunAsync(SmartSignalRequest request, CancellationToken cancellationToken) { // Read the signal's package this.tracer.TraceInformation($"Loading signal package for signal ID {request.SignalId}"); SmartSignalPackage signalPackage = await this.smartSignalRepository.ReadSignalPackageAsync(request.SignalId, cancellationToken); SmartSignalManifest signalManifest = signalPackage.Manifest; this.tracer.TraceInformation($"Read signal package, ID {signalManifest.Id}, Version {signalManifest.Version}"); // Load the signal ISmartSignal signal = this.smartSignalLoader.LoadSignal(signalPackage); this.tracer.TraceInformation($"Signal instance loaded successfully, ID {signalManifest.Id}"); // Get the resources on which to run the signal List <ResourceIdentifier> resources = await this.GetResourcesForSignal(request.ResourceIds, signalManifest, cancellationToken); // Run the signal this.tracer.TraceInformation($"Started running signal ID {signalManifest.Id}, Name {signalManifest.Name}"); SmartSignalResult signalResult; try { var analysisRequest = new AnalysisRequest(resources, request.LastExecutionTime, request.Cadence, this.analysisServicesFactory); signalResult = await signal.AnalyzeResourcesAsync(analysisRequest, this.tracer, cancellationToken); this.tracer.TraceInformation($"Completed running signal ID {signalManifest.Id}, Name {signalManifest.Name}, returning {signalResult.ResultItems.Count} result items"); } catch (Exception e) { this.tracer.TraceInformation($"Failed running signal ID {signalManifest.Id}, Name {signalManifest.Name}: {e.Message}"); throw new SmartSignalCustomException(e.GetType().ToString(), e.Message, e.StackTrace); } // Verify that each result item belongs to one of the types declared in the signal manifest foreach (SmartSignalResultItem resultItem in signalResult.ResultItems) { if (!signalManifest.SupportedResourceTypes.Contains(resultItem.ResourceIdentifier.ResourceType)) { throw new UnidentifiedResultItemResourceTypeException(resultItem.ResourceIdentifier); } } // Trace the number of result items of each type foreach (var resultItemType in signalResult.ResultItems.GroupBy(x => x.GetType().Name)) { this.tracer.TraceInformation($"Got {resultItemType.Count()} Smart Signal result items of type '{resultItemType.Key}'"); this.tracer.ReportMetric("SignalResultItemType", resultItemType.Count(), new Dictionary <string, string>() { { "ResultItemType", resultItemType.Key } }); } // Create results List <SmartSignalResultItemPresentation> results = new List <SmartSignalResultItemPresentation>(); foreach (var resultItem in signalResult.ResultItems) { SmartSignalResultItemQueryRunInfo queryRunInfo = await this.queryRunInfoProvider.GetQueryRunInfoAsync(new List <ResourceIdentifier>() { resultItem.ResourceIdentifier }, cancellationToken); results.Add(SmartSignalResultItemPresentation.CreateFromResultItem(request, signalManifest.Name, resultItem, queryRunInfo)); } this.tracer.TraceInformation($"Returning {results.Count} results"); return(results); }
/// <summary> /// Sends an HTTP request to the analysis function with the smart signal request /// </summary> /// <param name="analysisRequest">The request to send to the analysis function</param> /// <returns>A list of smart signal result items</returns> private async Task <IList <SmartSignalResultItemPresentation> > SendToAnalysisAsync(SmartSignalRequest analysisRequest) { var requestMessage = new HttpRequestMessage(HttpMethod.Post, this.analysisUrl); string requestBody = JsonConvert.SerializeObject(analysisRequest); requestMessage.Content = new StringContent(requestBody, Encoding.UTF8, "application/json"); this.tracer.TraceVerbose($"Sending analysis request {requestBody}"); // Send the request var response = await this.retryPolicy.RunAndTrackDependencyAsync(this.tracer, DependencyName, analysisRequest.SignalId, () => this.httpClientWrapper.SendAsync(requestMessage, default(CancellationToken))); if (!response.IsSuccessStatusCode) { string content = response.Content != null ? await response.Content.ReadAsStringAsync() : string.Empty; var message = $"Failed to execute signal {analysisRequest.SignalId}. Fail StatusCode: {response.StatusCode}. Reason: {response.ReasonPhrase}. Content: {content}."; throw new AnalysisExecutionException(message); } var httpAnalysisResult = await response.Content.ReadAsStringAsync(); return(JsonConvert.DeserializeObject <IList <SmartSignalResultItemPresentation> >(httpAnalysisResult)); }
/// <summary> /// Executes the signal via the analysis flow /// </summary> /// <param name="signalExecutionInfo">The signal execution information</param> /// <param name="resourceIds">The resource IDs used by the signal</param> /// <returns>A list of smart signal result items</returns> public async Task <IList <SmartSignalResultItemPresentation> > ExecuteSignalAsync(SignalExecutionInfo signalExecutionInfo, IList <string> resourceIds) { var analysisRequest = new SmartSignalRequest(resourceIds, signalExecutionInfo.AlertRule.SignalId, signalExecutionInfo.LastExecutionTime, signalExecutionInfo.AlertRule.Cadence, null); return(await this.SendToAnalysisAsync(analysisRequest)); }
/// <summary> /// Creates a presentation from a result item /// </summary> /// <param name="request">The smart signal request</param> /// <param name="signalName">The signal name</param> /// <param name="smartSignalResultItem">The result item</param> /// <param name="queryRunInfo">The query run information</param> /// <returns>The presentation</returns> public static SmartSignalResultItemPresentation CreateFromResultItem(SmartSignalRequest request, string signalName, SmartSignalResultItem smartSignalResultItem, SmartSignalResultItemQueryRunInfo queryRunInfo) { // A null result item has null presentation if (smartSignalResultItem == null) { return(null); } // Create presentation elements for each result item property Dictionary <string, string> predicates = new Dictionary <string, string>(); List <SmartSignalResultItemPresentationProperty> presentationProperties = new List <SmartSignalResultItemPresentationProperty>(); SmartSignalResultItemPresentationProperty summaryChart = null; string summaryValue = null; string summaryDetails = null; Dictionary <string, string> rawProperties = new Dictionary <string, string>(); foreach (PropertyInfo property in smartSignalResultItem.GetType().GetProperties()) { // Get the property value string propertyValue = PropertyValueToString(property.GetValue(smartSignalResultItem)); rawProperties[property.Name] = propertyValue; // Check if this property is a predicate if (property.GetCustomAttribute <ResultItemPredicateAttribute>() != null) { predicates[property.Name] = propertyValue; } // Get the presentation attribute ResultItemPresentationAttribute attribute = property.GetCustomAttribute <ResultItemPresentationAttribute>(); if (attribute != null) { // Verify that if the entity is a chart or query, then query run information was provided if (queryRunInfo == null && (attribute.Section == ResultItemPresentationSection.Chart || attribute.Section == ResultItemPresentationSection.AdditionalQuery)) { throw new InvalidSmartSignalResultItemPresentationException($"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, smartSignalResultItem); string attributeInfoBalloon = Smart.Format(attribute.InfoBalloon, smartSignalResultItem); // Add presentation to the summary component if (attribute.Component.HasFlag(ResultItemPresentationComponent.Summary)) { if (attribute.Section == ResultItemPresentationSection.Chart) { // Verify there is at most one summary chart if (summaryChart != null) { throw new InvalidSmartSignalResultItemPresentationException("There can be at most one summary chart for each resultItem"); } // Create the summary chart presentation property summaryChart = new SmartSignalResultItemPresentationProperty(attributeTitle, propertyValue, attribute.Section, attributeInfoBalloon); } else if (attribute.Section == ResultItemPresentationSection.Property) { // Verify there is at most one summary presentation property if (summaryValue != null) { throw new InvalidSmartSignalResultItemPresentationException("There must be exactly one summary property for each resultItem"); } // Set summary presentation elements summaryValue = propertyValue; summaryDetails = attributeTitle; } else { throw new InvalidSmartSignalResultItemPresentationException($"Invalid section for summary property {property.Name}: {attribute.Section}"); } } // Add presentation to the details component if (attribute.Component.HasFlag(ResultItemPresentationComponent.Details)) { presentationProperties.Add(new SmartSignalResultItemPresentationProperty(attributeTitle, propertyValue, attribute.Section, attributeInfoBalloon)); } } } // Verify that a summary was provided if (summaryValue == null) { throw new InvalidSmartSignalResultItemPresentationException("There must be exactly one summary property for each result item"); } string id = string.Join("##", smartSignalResultItem.GetType().FullName, JsonConvert.SerializeObject(request), JsonConvert.SerializeObject(smartSignalResultItem)).Hash(); string resourceId = smartSignalResultItem.ResourceIdentifier.ToResourceId(); string correlationHash = string.Join("##", predicates.OrderBy(x => x.Key).Select(x => x.Key + "|" + x.Value)).Hash(); // Return the presentation object return(new SmartSignalResultItemPresentation( id, smartSignalResultItem.Title, new SmartSignalResultItemPresentationSummary(summaryValue, summaryDetails, summaryChart), resourceId, correlationHash, request.SignalId, signalName, DateTime.UtcNow, (int)request.Cadence.TotalMinutes, presentationProperties, rawProperties, queryRunInfo)); }
private void TestInitialize(ResourceType requestResourceType, ResourceType signalResourceType) { this.tracerMock = new Mock <ITracer>(); ResourceIdentifier resourceId; switch (requestResourceType) { case ResourceType.Subscription: resourceId = new ResourceIdentifier(requestResourceType, "subscriptionId", string.Empty, string.Empty); break; case ResourceType.ResourceGroup: resourceId = new ResourceIdentifier(requestResourceType, "subscriptionId", "resourceGroup", string.Empty); break; default: resourceId = new ResourceIdentifier(requestResourceType, "subscriptionId", "resourceGroup", "resourceName"); break; } this.resourceIds = new List <string>() { resourceId.ToResourceId() }; this.request = new SmartSignalRequest(this.resourceIds, "1", DateTime.UtcNow.AddDays(-1), TimeSpan.FromDays(1), new SmartSignalSettings()); var smartSignalManifest = new SmartSignalManifest("1", "Test signal", "Test signal description", Version.Parse("1.0"), "assembly", "class", new List <ResourceType>() { signalResourceType }, new List <int> { 60 }); this.smartSignalPackage = new SmartSignalPackage(smartSignalManifest, new Dictionary <string, byte[]> { ["TestSignalLibrary"] = new byte[0] }); this.smartSignalsRepositoryMock = new Mock <ISmartSignalRepository>(); this.smartSignalsRepositoryMock .Setup(x => x.ReadSignalPackageAsync(It.IsAny <string>(), It.IsAny <CancellationToken>())) .ReturnsAsync(() => this.smartSignalPackage); this.analysisServicesFactoryMock = new Mock <IAnalysisServicesFactory>(); this.signal = new TestSignal { ExpectedResourceType = signalResourceType }; this.smartSignalLoaderMock = new Mock <ISmartSignalLoader>(); this.smartSignalLoaderMock .Setup(x => x.LoadSignal(this.smartSignalPackage)) .Returns(this.signal); this.azureResourceManagerClientMock = new Mock <IAzureResourceManagerClient>(); this.azureResourceManagerClientMock .Setup(x => x.GetAllResourceGroupsInSubscriptionAsync(It.IsAny <string>(), It.IsAny <CancellationToken>())) .ReturnsAsync((string subscriptionId, CancellationToken cancellationToken) => new List <ResourceIdentifier>() { new ResourceIdentifier(ResourceType.ResourceGroup, subscriptionId, "resourceGroupName", string.Empty) }); this.azureResourceManagerClientMock .Setup(x => x.GetAllResourcesInSubscriptionAsync(It.IsAny <string>(), It.IsAny <IEnumerable <ResourceType> >(), It.IsAny <CancellationToken>())) .ReturnsAsync((string subscriptionId, IEnumerable <ResourceType> resourceTypes, CancellationToken cancellationToken) => new List <ResourceIdentifier>() { new ResourceIdentifier(ResourceType.VirtualMachine, subscriptionId, "resourceGroupName", "resourceName") }); this.azureResourceManagerClientMock .Setup(x => x.GetAllResourcesInResourceGroupAsync(It.IsAny <string>(), It.IsAny <string>(), It.IsAny <IEnumerable <ResourceType> >(), It.IsAny <CancellationToken>())) .ReturnsAsync((string subscriptionId, string resourceGroupName, IEnumerable <ResourceType> resourceTypes, CancellationToken cancellationToken) => new List <ResourceIdentifier>() { new ResourceIdentifier(ResourceType.VirtualMachine, subscriptionId, resourceGroupName, "resourceName") }); this.queryRunInfoProviderMock = new Mock <IQueryRunInfoProvider>(); }
/// <summary> /// Run the signal, by delegating the call to the registered <see cref="ISmartSignalRunner"/> /// </summary> /// <param name="request">The signal request</param> /// <param name="cancellationToken">The cancellation token</param> /// <returns>A <see cref="Task{TResult}"/>, returning the result items presentations generated by the signal</returns> private static async Task <List <SmartSignalResultItemPresentation> > RunSignalAsync(SmartSignalRequest request, CancellationToken cancellationToken) { ISmartSignalRunner smartSignalRunner = container.Resolve <ISmartSignalRunner>(); return(await smartSignalRunner.RunAsync(request, cancellationToken)); }