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);
                }
            }
        }
Example #3
0
        /// <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);
        }