public static async Task <DependencyFlowGraph> BuildAsync( List <DefaultChannel> defaultChannels, List <Subscription> subscriptions, IRemote barOnlyRemote, int days) { // Dictionary of nodes. Key is the repo+branch Dictionary <string, DependencyFlowNode> nodes = new Dictionary <string, DependencyFlowNode>( StringComparer.OrdinalIgnoreCase); List <DependencyFlowEdge> edges = new List <DependencyFlowEdge>(); // First create all the channel nodes. There may be disconnected // nodes in the graph, so we must process all channels and all subscriptions foreach (DefaultChannel channel in defaultChannels) { DependencyFlowNode flowNode = GetOrCreateNode(channel.Repository, channel.Branch, nodes); // Add the build times if (channel.Id != default(int)) { BuildTime buildTime = await barOnlyRemote.GetBuildTimeAsync(channel.Id, days); flowNode.OfficialBuildTime = buildTime.OfficialBuildTime ?? 0; flowNode.PrBuildTime = buildTime.PrBuildTime ?? 0; flowNode.GoalTimeInMinutes = buildTime.GoalTimeInMinutes ?? 0; } else { flowNode.OfficialBuildTime = 0; flowNode.PrBuildTime = 0; } // Add a the output mapping. flowNode.OutputChannels.Add(channel.Channel.Name); } // Process all subscriptions (edges) foreach (Subscription subscription in subscriptions) { // Get the target of the subscription DependencyFlowNode destinationNode = GetOrCreateNode(subscription.TargetRepository, subscription.TargetBranch, nodes); // Add the input channel for the node destinationNode.InputChannels.Add(subscription.Channel.Name); // Find all input nodes by looking up the default channels of the subscription input channel and repository. // This may return no nodes if there is no default channel for the inputs. IEnumerable <DefaultChannel> inputDefaultChannels = defaultChannels.Where(d => d.Channel.Name == subscription.Channel.Name && d.Repository.Equals(subscription.SourceRepository, StringComparison.OrdinalIgnoreCase)); foreach (DefaultChannel defaultChannel in inputDefaultChannels) { DependencyFlowNode sourceNode = GetOrCreateNode(defaultChannel.Repository, defaultChannel.Branch, nodes); DependencyFlowEdge newEdge = new DependencyFlowEdge(sourceNode, destinationNode, subscription); destinationNode.IncomingEdges.Add(newEdge); sourceNode.OutgoingEdges.Add(newEdge); edges.Add(newEdge); } } return(new DependencyFlowGraph(nodes.Select(kv => kv.Value).ToList(), edges)); }
public void RemoveEdge(DependencyFlowEdge edge) { if (Edges.Remove(edge)) { edge.From.OutgoingEdges.Remove(edge); DependencyFlowNode targetNode = edge.To; edge.To.IncomingEdges.Remove(edge); RecalculateInputChannels(targetNode); } }
/// <summary> /// If pruning the graph is desired, determine whether an edge is interesting /// </summary> /// <param name="edge">Edge</param> /// <returns>True if the edge is interesting, false otherwise.</returns> public static bool IsInterestingEdge(DependencyFlowEdge edge, bool includeDisabledSubscriptions, IEnumerable <string> includedFrequencies) { if (!includeDisabledSubscriptions && !edge.Subscription.Enabled) { return(false); } if (!includedFrequencies.Any(s => s.Equals(edge.Subscription.Policy.UpdateFrequency.ToString(), StringComparison.OrdinalIgnoreCase))) { return(false); } return(true); }
private void MarkLongestPath(DependencyFlowNode node) { // The edges we are interested in are those that haven't been marked as on the longest build path // and aren't back edges, both of which indicate a cycle var edgesOfInterest = node.OutgoingEdges.Where(e => !e.OnLongestBuildPath && !e.BackEdge).ToList(); if (edgesOfInterest.Count > 0) { DependencyFlowEdge pathEdge = edgesOfInterest.Aggregate((e1, e2) => e1.To.BestCasePathTime > e2.To.BestCasePathTime ? e1: e2); // Mark the edge and the node as on the longest build path pathEdge.OnLongestBuildPath = true; pathEdge.To.OnLongestBuildPath = true; MarkLongestPath(pathEdge.To); } }
/// <summary> /// Mark the back edges of the graph so that they can be ignored when walking it. /// Determine the backedges by computing dominators for each node. A node n is said to /// dominate another node if every path from the start to the other node must go through /// n. /// </summary> public void MarkBackEdges() { // In this, we will interpret the root of the graph as those nodes that have no outgoing edges // (call them 'products'). Connect all of these via a start node. // Add a start node to the graph (a node that connects all nodes that have no outgoing edges // We will also reverse the graph by flipping the interpretation of incoming and outgoing. DependencyFlowNode startNode = new DependencyFlowNode("start", "start", "start"); foreach (DependencyFlowNode node in Nodes) { if (!node.OutgoingEdges.Any()) { DependencyFlowEdge newEdge = new DependencyFlowEdge(node, startNode, null); startNode.IncomingEdges.Add(newEdge); node.OutgoingEdges.Add(newEdge); } } Nodes.Add(startNode); // Dominator set for each node starts with the full set of nodes. Dictionary <DependencyFlowNode, HashSet <DependencyFlowNode> > dominators = new Dictionary <DependencyFlowNode, HashSet <DependencyFlowNode> >(); foreach (DependencyFlowNode node in Nodes) { dominators.Add(node, new HashSet <DependencyFlowNode>(Nodes)); } Queue <DependencyFlowNode> workList = new Queue <DependencyFlowNode>(); workList.Enqueue(startNode); while (workList.Count != 0) { DependencyFlowNode currentNode = workList.Dequeue(); // Compute a new dominator set for this node. Remember we are // flipping the interepretation of incoming and outgoing HashSet <DependencyFlowNode> newDom = null; IEnumerable <DependencyFlowNode> predecessors = currentNode.OutgoingEdges.Select(e => e.To); IEnumerable <DependencyFlowNode> successors = currentNode.IncomingEdges.Select(e => e.From); // Compute the intersection of the dominators for the predecessors foreach (DependencyFlowNode predNode in predecessors) { if (newDom == null) { newDom = new HashSet <DependencyFlowNode>(dominators[predNode]); } else { newDom.IntersectWith(dominators[predNode]); } } if (newDom == null) { newDom = new HashSet <DependencyFlowNode>(); } // Add the current node newDom.Add(currentNode); // Compare to the existing dominator set for this node if (!dominators[currentNode].SetEquals(newDom)) { dominators[currentNode] = newDom; // Queue all of the successors foreach (DependencyFlowNode succ in successors) { workList.Enqueue(succ); } } } // Determine backedges List <DependencyFlowEdge> toRemove = new List <DependencyFlowEdge>(); foreach (DependencyFlowEdge edge in Edges) { if (dominators[edge.To].Contains(edge.From)) { edge.BackEdge = true; } } // Remove the start node we created and links to it foreach (DependencyFlowEdge edge in startNode.IncomingEdges) { RemoveEdge(edge); } RemoveNode(startNode); }