public string AlertActionExecute(long alertId, int alertActionId) { var jsonResponse = new List <object>(); try { // Lookup the alert. ContosoAlert alert = Startup.Topology.FindAlert(alertId); if (alert != null) { // Validate the action id. ContosoTopologyNode topologyNode = Startup.Topology[alert.Key] as ContosoTopologyNode; List <ContosoAlertActionDefinition> alertActions = topologyNode.GetAlertActions(alert.Cause, alert.SubKey); if (alertActions == null || alertActionId >= alertActions.Count) { Trace.TraceError($"alertActionId '{alertActionId}' is out of scope or unknown."); Response.StatusCode = 1; jsonResponse.Add(new { errorMessage = Strings.AlertIdUnknown }); } else { bool updateSessionAlerts = false; // Process the requested action. switch (alertActions[alertActionId].Type) { case ContosoAlertActionType.AcknowledgeAlert: // Update alert status. if (topologyNode.AcknowledgeAlert(alertId) == false) { Trace.TraceError( $"alertId '{alertId}' in node '{topologyNode.Key}' could not be acknowledged."); Response.StatusCode = 1; jsonResponse.Add(new { errorMessage = Strings.AlertIdUnknown }); } updateSessionAlerts = true; jsonResponse.Add(new { actionType = ContosoAlertActionType.AcknowledgeAlert }); break; case ContosoAlertActionType.CloseAlert: // Update alert status. if (topologyNode.CloseAlert(alertId) == false) { Trace.TraceError( $"alertId '{alertId}' in node '{topologyNode.Key}' could not be acknowledged."); Response.StatusCode = 1; jsonResponse.Add(new { errorMessage = Strings.AlertIdUnknown }); } updateSessionAlerts = true; jsonResponse.Add(new { actionType = ContosoAlertActionType.CloseAlert }); break; case ContosoAlertActionType.CallOpcMethod: // Validate that this is a OPC UA server. if (topologyNode.GetType() != typeof(Station)) { Trace.TraceError($"Toplogy node '{topologyNode.Key}' is not an OPC UA server. No method call possible."); Response.StatusCode = 1; jsonResponse.Add(new { errorMessage = Strings.AlertIdUnknown }); break; } // Parameter format: "<parent nodeId>, <method nodeId>, <opcua server uri>" string[] parameter = alertActions[alertActionId].Parameter.Split(','); jsonResponse.Add(new { errorMessage = CallOpcMethod(parameter[2].Trim(), parameter[0].Trim(), parameter[1].Trim()) }); break; case ContosoAlertActionType.OpenWebPage: jsonResponse.Add(new { actionType = ContosoAlertActionType.OpenWebPage }); string urlPath = alertActions[alertActionId].Parameter; jsonResponse.Add(new { url = urlPath }); break; case ContosoAlertActionType.None: default: Trace.TraceWarning($"alert type '{alertActions[alertActionId].Type}' of alert '{alertId}' resulted in no action."); break; } // Update session alerts if requested. if (updateSessionAlerts) { // Prepare the updated alert list. AlertDataUpdate[] sessionAlertDataUpdate = new AlertDataUpdate[Startup.SessionList.Count]; int sessionUpdateIndex = 0; foreach (KeyValuePair <string, DashboardModel> session in Startup.SessionList) { AlertDataUpdate sessionAlertData = new AlertDataUpdate(); string topNodeKey = session.Value.TopNode.Key; // Update the alert data. sessionAlertData.SessionId = Session.SessionID; sessionAlertData.TopNode = topNodeKey; sessionAlertData.Alerts = Startup.Topology.GetAlerts(topNodeKey); // Update the data sent to the clients. sessionAlertDataUpdate[sessionUpdateIndex] = sessionAlertData; sessionUpdateIndex++; } // Update all clients string sessionAlertDataUpdateJson = JsonConvert.SerializeObject(sessionAlertDataUpdate); if (Startup.SessionList.Count > 0 && sessionUpdateIndex > 0) { IHubContext hubContext = GlobalHost.ConnectionManager.GetHubContext <TelemetryHub>(); hubContext.Clients.All.updateSessionAlertData(sessionAlertDataUpdateJson); } } } } else { Trace.TraceError($"alertId '{alertId}' is out of scope or unknown."); Response.StatusCode = 1; jsonResponse.Add(new { errorMessage = Strings.AlertIdUnknown }); } } catch (Exception ex) { Trace.TraceError($"exception '{ex.Message}' while processing alertId '{alertId}' and alertActionId '{alertActionId}"); Response.StatusCode = 1; jsonResponse.Add(new { errorMessage = Strings.AlertIdUnknown }); } return(JsonConvert.SerializeObject(jsonResponse)); }
/// <summary> /// Worker to query one aggregate of a topology /// </summary> /// <param name="aggregateIndex">The aggregation view to update by the worker</param> /// <param name="token">CancellationToken</param> public async Task Worker(int aggregateIndex, CancellationToken token) { RDXOpcUaQueries opcUaQueries = new RDXOpcUaQueries(token); RDXOeeKpiQueries oeeKpiQueries = new RDXOeeKpiQueries(opcUaQueries, token); RDXTrace.TraceInformation("RDX Worker {0} started", aggregateIndex); // give app some time to start before updating queries await Task.Delay(10000 *(aggregateIndex + 1)); while (!token.IsCancellationRequested) { Stopwatch stopWatch = new Stopwatch(); DateTime nextUpdate = DateTime.MaxValue; bool resetStartDelayed = false; stopWatch.Start(); Interlocked.Increment(ref _busyWorkers); // the station and node list is updated, other workers are delayed while (_workerStartDelayed != 0) { RDXTrace.TraceInformation("RDX Worker {0} delayed", aggregateIndex); await Task.Delay(1000); } try { List <Task> tasks = new List <Task>(); ContosoTopologyNode rootNode = _topology.GetRootNode(); ContosoAggregatedOeeKpiHistogram aggregatedTimeSpan = rootNode[aggregateIndex]; DateTimeRange searchSpan = RDXUtils.TotalSearchRangeFromNow(aggregatedTimeSpan); RDXTrace.TraceInformation("RDX Worker {0} updating Range {1} to {2}", aggregateIndex, searchSpan.From, searchSpan.To); // calc next update. To time is already rounded for update time span. nextUpdate = searchSpan.To + rootNode[aggregateIndex].UpdateTimeSpan; // query all stations in topology and find all active servers in timespan Task <StringDimensionResult> aggServerTask = opcUaQueries.AggregateServers(searchSpan); // always get an aggregate of all activity for the latest interval, use it as a cache TimeSpan intervalTimeSpan = aggregatedTimeSpan[0].IntervalTimeSpan; DateTimeRange aggregateSpan = RDXUtils.CalcAggregationRange(aggregatedTimeSpan, searchSpan.To); RDXCachedAggregatedQuery fullQuery = new RDXCachedAggregatedQuery(opcUaQueries); Task aggServerAndNodesTask = fullQuery.Execute(aggregateSpan); // wait for all outstanding aggregates tasks.Add(aggServerTask); tasks.Add(aggServerAndNodesTask); await RDXUtils.WhenAllTasks("Aggregates", tasks, stopWatch); fullQuery.UpdateCacheQueryResult(); List <string> topologyStations = _topology.GetAllChildren(_tree.TopologyRoot.Key, typeof(Station)); List <string> opcUaServers = await aggServerTask; // intersect list of active servers and schedule all queries tasks.Clear(); List <string> opcUaServersToQuery = opcUaServers.Intersect(topologyStations, StringComparer.InvariantCultureIgnoreCase).ToList(); // query known servers await oeeKpiQueries.ScheduleAllOeeKpiQueries(searchSpan, _topology, fullQuery, opcUaServersToQuery, tasks, aggregateIndex); // wait for all outstanding queries await RDXUtils.WhenAllTasks("Queries", tasks, stopWatch); // Update the topology Oee and KPI values _topology.UpdateAllKPIAndOEEValues(aggregateIndex); // one worker issues the Browser update if (aggregatedTimeSpan.UpdateBrowser) { // Push updates to dashboard try { TriggerSessionOeeKpiDataUpdate(); TriggerSessionAlertDataUpdate(); TriggerSessionChildrenDataUpdate(); } catch (Exception e) { RDXTrace.TraceError($"Exception {e.Message} in Worker while updating browser sessions"); } } // add new stations and nodes to topology if (aggregatedTimeSpan.UpdateTopology) { // delay other workers Interlocked.Exchange(ref _workerStartDelayed, 1); resetStartDelayed = true; // only add stations and nodes if no other worker is busy yet if (Interlocked.Increment(ref _busyWorkers) == 2) { AddNewServers(fullQuery, topologyStations); await AddNewNodes(fullQuery, topologyStations); RDXTrace.TraceInformation("Add New Server and Nodes finished after {0}ms", stopWatch.ElapsedMilliseconds); } } } catch (Exception e) { RDXTrace.TraceError("Exception {0} in Worker after {1}ms", e.Message, stopWatch.ElapsedMilliseconds); } finally { Interlocked.Decrement(ref _busyWorkers); if (resetStartDelayed) { Interlocked.Decrement(ref _busyWorkers); Interlocked.Exchange(ref _workerStartDelayed, 0); } } RDXTrace.TraceInformation("RDX Worker {0} schedule next update for {1}", aggregateIndex, nextUpdate); TimeSpan delay = nextUpdate.Subtract(DateTime.UtcNow); if (delay.TotalMilliseconds > 0) { await Task.Delay(delay, token); } } }
/// <summary> /// Test alert condition for nodes /// </summary> /// <param name="searchSpan"></param> /// <param name="appUri"></param> /// <param name="topologyNode"></param> /// <param name="node"></param> public async Task <ContosoPerformanceStatus> CheckAlert(DateTimeRange searchSpan, string appUri, ContosoTopologyNode topologyNode, ContosoOpcUaNode node) { double value = await _opcUaQueries.GetLatestQuery(searchSpan.To, appUri, node.NodeId); // Check for an alert condition. if (node.Minimum != null && value < node.Minimum) { // Add an alert to the server this OPC UA node belongs to. ContosoAlert alert = new ContosoAlert(ContosoAlertCause.AlertCauseValueBelowMinimum, appUri, node.NodeId, searchSpan.To); topologyNode.AddAlert(alert); node.Status = ContosoPerformanceStatus.Poor; } else if (node.Maximum != null && value > node.Maximum) { // Add an alert to the server this OPC UA node belongs to. ContosoAlert alert = new ContosoAlert(ContosoAlertCause.AlertCauseValueAboveMaximum, appUri, node.NodeId, searchSpan.To); topologyNode.AddAlert(alert); node.Status = ContosoPerformanceStatus.Poor; } else { node.Status = ContosoPerformanceStatus.Good; } return(node.Status); }