/// <summary> /// Queries all intervals, servers and nodes in a topology for new values. /// Updates all relevances and checks for alerts. /// Returns list of task. /// Caller must wait for tasks to run to completion before topology is fully up to date. /// </summary> /// <param name="totalSearchSpan">Search span of query</param> /// <param name="topology">Topology to update</param> /// <param name="fullQuery">The cached query for the search span</param> /// <param name="opcUaServers">List of OPC UA servers to query for values</param> /// <param name="tasks">List of async tasks processing queries</param> /// <param name="aggregateIndex">The index of the aggregate in the topology nodes</param> public async Task ScheduleAllOeeKpiQueries( DateTimeRange totalSearchSpan, ContosoTopology topology, RDXCachedAggregatedQuery fullQuery, List <string> opcUaServers, List <Task> tasks, int aggregateIndex) { RDXAggregatedQueryCache queryCache = new RDXAggregatedQueryCache(); queryCache.List.Add(fullQuery); foreach (string appUri in opcUaServers) { Station station = topology[appUri] as Station; if (station != null) { ContosoAggregatedOeeKpiHistogram oeeKpiHistogram = station[aggregateIndex]; DateTime toTime = totalSearchSpan.To; int intervals = oeeKpiHistogram.Intervals.Count; oeeKpiHistogram.EndTime = toTime; station[aggregateIndex].EndTime = toTime; foreach (ContosoAggregatedOeeKpiTimeSpan oeeKpiTimeSpan in oeeKpiHistogram.Intervals) { DateTime fromTime; // first interval is current time if (totalSearchSpan.To != toTime || intervals == 1) { fromTime = toTime.Subtract(oeeKpiTimeSpan.IntervalTimeSpan); } else { fromTime = RDXUtils.RoundDateTimeToTimeSpan(toTime.Subtract(TimeSpan.FromSeconds(1)), oeeKpiTimeSpan.IntervalTimeSpan); } DateTimeRange intervalSearchSpan = new DateTimeRange(fromTime, toTime); if (toTime <= oeeKpiTimeSpan.EndTime) { // The interval is still up to date from a previous query, skip toTime = fromTime; continue; } oeeKpiTimeSpan.EndTime = toTime; toTime = fromTime; // find a cached query, if not try to cache the aggregation for this search span RDXCachedAggregatedQuery aggregatedQuery = queryCache.Find(intervalSearchSpan); if (aggregatedQuery == null) { RDXCachedAggregatedQuery newQuery = new RDXCachedAggregatedQuery(_opcUaQueries); queryCache.List.Add(newQuery); tasks.Add(newQuery.Execute(intervalSearchSpan)); aggregatedQuery = queryCache.Find(intervalSearchSpan); } RDXOeeKpiQuery oeeKpiQuery = new RDXOeeKpiQuery(_opcUaQueries, intervalSearchSpan, appUri, oeeKpiTimeSpan); await oeeKpiQuery.QueryAllNodes(tasks, station.NodeList, oeeKpiHistogram.AwaitTasks, aggregatedQuery); if (oeeKpiHistogram.CheckAlerts) { station.Status = ContosoPerformanceStatus.Good; foreach (ContosoOpcUaNode node in station.NodeList) { if (node.Minimum != null || node.Maximum != null) { station.Status |= await CheckAlert(intervalSearchSpan, appUri, station, node); } } } } } } }
/// <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); 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(); 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 BrowserUpdate(); RDXTrace.TraceInformation("BrowserUpdate finished after {0}ms", stopWatch.ElapsedMilliseconds); } // 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); } } }