/// <summary>
        ///     Log the graph in graphviz (dot) format.
        /// </summary>
        /// <param name="graph">Graph to log</param>
        /// <remarks>
        /// Example of a graphviz graph description
        ///
        /// digraph graphname {
        ///    a -> b -> c;
        ///    b -> d;
        /// }
        ///
        /// For more info see https://www.graphviz.org/
        /// </remarks>
        /// <returns>Async task</returns>
        private async Task LogGraphVizAsync(Channel targetChannel, DependencyFlowGraph graph, bool includeBuildTimes)
        {
            StringBuilder subgraphClusterWriter  = null;
            bool          writeToSubgraphCluster = targetChannel != null;

            if (writeToSubgraphCluster)
            {
                subgraphClusterWriter = new StringBuilder();
                subgraphClusterWriter.AppendLine($"    subgraph cluster_{UxHelpers.CalculateGraphVizNodeName(targetChannel.Name)} {{");
                subgraphClusterWriter.AppendLine($"        label = \"{targetChannel.Name}\"");
            }

            using (StreamWriter writer = UxHelpers.GetOutputFileStreamOrConsole(_options.GraphVizOutputFile))
            {
                await writer.WriteLineAsync("digraph repositoryGraph {");

                await writer.WriteLineAsync("    node [shape=record]");

                foreach (DependencyFlowNode node in graph.Nodes)
                {
                    StringBuilder nodeBuilder = new StringBuilder();

                    string style = node.OnLongestBuildPath ? "style=\"diagonals,bold\" color=red" : "";

                    // First add the node name
                    nodeBuilder.Append($"    {UxHelpers.CalculateGraphVizNodeName(node)}");

                    // Then add the label.  label looks like [label="<info here>"]
                    nodeBuilder.Append($"[{style}\nlabel=\"");

                    // Append friendly repo name
                    nodeBuilder.Append(UxHelpers.GetSimpleRepoName(node.Repository));
                    nodeBuilder.Append(@"\n");

                    // Append branch name
                    nodeBuilder.Append(node.Branch);

                    if (includeBuildTimes)
                    {
                        // Append best case
                        nodeBuilder.Append(@"\n");
                        nodeBuilder.Append($"Best Case: {Math.Round(node.BestCasePathTime, 2, MidpointRounding.AwayFromZero)} min");
                        nodeBuilder.Append(@"\n");

                        // Append worst case
                        nodeBuilder.Append($"Worst Case: {Math.Round(node.WorstCasePathTime, 2, MidpointRounding.AwayFromZero)} min");
                        nodeBuilder.Append(@"\n");

                        // Append build times
                        nodeBuilder.Append($"Official Build Time: {Math.Round(node.OfficialBuildTime, 2, MidpointRounding.AwayFromZero)} min");
                        nodeBuilder.Append(@"\n");

                        nodeBuilder.Append($"PR Build Time: {Math.Round(node.PrBuildTime, 2, MidpointRounding.AwayFromZero)} min");
                    }

                    // Append end of label and end of node.
                    nodeBuilder.Append("\"];");

                    // If highlighting a specific channel, Add those nodes to a subgraph cluster
                    // if they output to the subgraph cluster.
                    if (writeToSubgraphCluster && node.OutputChannels.Any(c => c == targetChannel.Name))
                    {
                        subgraphClusterWriter.AppendLine($"        {UxHelpers.CalculateGraphVizNodeName(node)}");
                    }

                    // Write it out.
                    await writer.WriteLineAsync(nodeBuilder.ToString());
                }

                // Now write all the edges
                foreach (DependencyFlowEdge edge in graph.Edges)
                {
                    string fromNode = UxHelpers.CalculateGraphVizNodeName(edge.From);
                    string toNode   = UxHelpers.CalculateGraphVizNodeName(edge.To);
                    string label    = $"{edge.Subscription.Channel.Name} ({edge.Subscription.Policy.UpdateFrequency})";

                    if (writeToSubgraphCluster && edge.Subscription.Channel.Name == targetChannel.Name)
                    {
                        subgraphClusterWriter.AppendLine($"    {fromNode} -> {toNode} [{GetEdgeStyle(edge)}]");
                    }
                    else
                    {
                        await writer.WriteLineAsync($"    {fromNode} -> {toNode} [{GetEdgeStyle(edge)}]");
                    }
                }

                if (writeToSubgraphCluster)
                {
                    await writer.WriteLineAsync(subgraphClusterWriter.ToString());

                    await writer.WriteLineAsync("    }");
                }

                // Write a legend

                await writer.WriteLineAsync("    subgraph cluster1 {");

                await writer.WriteLineAsync("        rankdir=RL;");

                await writer.WriteLineAsync("        label = \"Legend\"");

                await writer.WriteLineAsync("        shape = rectangle;");

                await writer.WriteLineAsync("        color = black;");

                await writer.WriteLineAsync("        a[style = invis];");

                await writer.WriteLineAsync("        b[style = invis];");

                await writer.WriteLineAsync("        c[style = invis];");

                await writer.WriteLineAsync("        d[style = invis];");

                await writer.WriteLineAsync("        e[style = invis];");

                await writer.WriteLineAsync("        f[style = invis];");

                await writer.WriteLineAsync("        g[style = \"diagonals,bold\" color=red];");

                await writer.WriteLineAsync("        h[style = \"diagonals,bold\" color=red];");

                await writer.WriteLineAsync("        c->d[label = \"Updated Every Build\", style = bold];");

                await writer.WriteLineAsync("        a->b[label = \"Updated Every Day\", style = dashed];");

                await writer.WriteLineAsync("        e->f[label = \"Disabled/Updated On-demand\", style = dotted];");

                await writer.WriteLineAsync("        g->h[label = \"Longest Build Path\", color=\"red:invis:red\"];");

                await writer.WriteLineAsync("    }");

                await writer.WriteLineAsync("    subgraph cluster2{");

                await writer.WriteLineAsync("        rankdir=BT;");

                await writer.WriteLineAsync("        style=invis;");

                await writer.WriteLineAsync("        note[shape=plaintext label=\"Best Case: Time through the graph assuming no dependency flow\nWorst Case: Time through the graph with dependency flow (PRs)\"];");

                await writer.WriteLineAsync("    }");

                await writer.WriteLineAsync("    d->note[lhead=cluster2, ltail=cluster1, style=invis];");


                await writer.WriteLineAsync("}");
            }
        }
Ejemplo n.º 2
0
        /// <summary>
        ///     Log the graph in graphviz (dot) format.
        /// </summary>
        /// <param name="graph">Graph to log</param>
        /// <remarks>
        /// Example of a graphviz graph description
        ///
        /// digraph graphname {
        ///    a -> b -> c;
        ///    b -> d;
        /// }
        ///
        /// For more info see https://www.graphviz.org/
        /// </remarks>
        /// <returns>Async task</returns>
        private async Task LogGraphViz(DependencyGraph graph)
        {
            using (StreamWriter writer = UxHelpers.GetOutputFileStreamOrConsole(_options.GraphVizOutputFile))
            {
                await writer.WriteLineAsync("digraph repositoryGraph {");

                await writer.WriteLineAsync("    node [shape=record]");

                foreach (DependencyGraphNode node in graph.Nodes)
                {
                    StringBuilder nodeBuilder = new StringBuilder();

                    // First add the node name
                    nodeBuilder.Append($"    {UxHelpers.CalculateGraphVizNodeName(node)}");

                    // Then add the label.  label looks like [label="<info here>"]
                    nodeBuilder.Append("[label=\"");

                    // Append friendly repo name
                    nodeBuilder.Append(UxHelpers.GetSimpleRepoName(node.Repository));
                    nodeBuilder.Append(@"\n");

                    // Append short commit sha
                    nodeBuilder.Append(node.Commit.Substring(0, node.Commit.Length < 10 ? node.Commit.Length : 10));

                    // Append a build string (with newline) if available
                    if (node.ContributingBuilds != null && node.ContributingBuilds.Any())
                    {
                        Build newestBuild = node.ContributingBuilds.OrderByDescending(b => b.DateProduced).First();
                        nodeBuilder.Append($"\\n{newestBuild.DateProduced.ToString("g")} (UTC)");
                    }

                    // Append a diff string if the graph contains diff info.
                    GitDiff diffFrom = node.DiffFrom;
                    if (diffFrom != null)
                    {
                        if (!diffFrom.Valid)
                        {
                            nodeBuilder.Append("\\ndiff unknown");
                        }
                        else if (diffFrom.Ahead != 0 || diffFrom.Behind != 0)
                        {
                            if (node.DiffFrom.Ahead != 0)
                            {
                                nodeBuilder.Append($"\\nahead: {node.DiffFrom.Ahead} commits");
                            }
                            if (node.DiffFrom.Behind != 0)
                            {
                                nodeBuilder.Append($"\\nbehind: {node.DiffFrom.Behind} commits");
                            }
                        }
                        else
                        {
                            nodeBuilder.Append("\\nlatest");
                        }
                    }

                    // Append end of label and end of node.
                    nodeBuilder.Append("\"];");

                    // Write it out.
                    await writer.WriteLineAsync(nodeBuilder.ToString());

                    // Now write the edges
                    foreach (DependencyGraphNode childNode in node.Children)
                    {
                        await writer.WriteLineAsync($"    {UxHelpers.CalculateGraphVizNodeName(node)} -> {UxHelpers.CalculateGraphVizNodeName(childNode)}");
                    }
                }

                await writer.WriteLineAsync("}");
            }
        }
        /// <summary>
        ///     Log the graph in graphviz (dot) format.
        /// </summary>
        /// <param name="graph">Graph to log</param>
        /// <remarks>
        /// Example of a graphviz graph description
        ///
        /// digraph graphname {
        ///    a -> b -> c;
        ///    b -> d;
        /// }
        ///
        /// For more info see https://www.graphviz.org/
        /// </remarks>
        /// <returns>Async task</returns>
        private async Task LogGraphViz(Channel targetChannel, DependencyFlowGraph graph)
        {
            StringBuilder subgraphClusterWriter  = null;
            bool          writeToSubgraphCluster = targetChannel != null;

            if (writeToSubgraphCluster)
            {
                subgraphClusterWriter = new StringBuilder();
                subgraphClusterWriter.AppendLine($"    subgraph cluster_{UxHelpers.CalculateGraphVizNodeName(targetChannel.Name)} {{");
                subgraphClusterWriter.AppendLine($"        label = \"{targetChannel.Name}\"");
            }

            using (StreamWriter writer = UxHelpers.GetOutputFileStreamOrConsole(_options.GraphVizOutputFile))
            {
                await writer.WriteLineAsync("digraph repositoryGraph {");

                await writer.WriteLineAsync("    node [shape=record]");

                foreach (DependencyFlowNode node in graph.Nodes)
                {
                    StringBuilder nodeBuilder = new StringBuilder();

                    // First add the node name
                    nodeBuilder.Append($"    {UxHelpers.CalculateGraphVizNodeName(node)}");

                    // Then add the label.  label looks like [label="<info here>"]
                    nodeBuilder.Append("[label=\"");

                    // Append friendly repo name
                    nodeBuilder.Append(UxHelpers.GetSimpleRepoName(node.Repository));
                    nodeBuilder.Append(@"\n");

                    // Append branch name
                    nodeBuilder.Append(node.Branch);

                    // Append end of label and end of node.
                    nodeBuilder.Append("\"];");

                    // If highlighting a specific channel, Add those nodes to a subgraph cluster
                    // if they output to the subgraph cluster.
                    if (writeToSubgraphCluster && node.OutputChannels.Any(c => c == targetChannel.Name))
                    {
                        subgraphClusterWriter.AppendLine($"        {UxHelpers.CalculateGraphVizNodeName(node)}");
                    }

                    // Write it out.
                    await writer.WriteLineAsync(nodeBuilder.ToString());
                }

                // Now write all the edges
                foreach (DependencyFlowEdge edge in graph.Edges)
                {
                    string fromNode = UxHelpers.CalculateGraphVizNodeName(edge.From);
                    string toNode   = UxHelpers.CalculateGraphVizNodeName(edge.To);
                    string label    = $"{edge.Subscription.Channel.Name} ({edge.Subscription.Policy.UpdateFrequency})";

                    if (writeToSubgraphCluster && edge.Subscription.Channel.Name == targetChannel.Name)
                    {
                        subgraphClusterWriter.AppendLine($"    {fromNode} -> {toNode} [{GetEdgeStyle(edge)}]");
                    }
                    else
                    {
                        await writer.WriteLineAsync($"    {fromNode} -> {toNode} [{GetEdgeStyle(edge)}]");
                    }
                }

                if (writeToSubgraphCluster)
                {
                    await writer.WriteLineAsync(subgraphClusterWriter.ToString());

                    await writer.WriteLineAsync("    }");
                }

                // Write a legend

                await writer.WriteLineAsync("    subgraph cluster1 {");

                await writer.WriteLineAsync("        rankdir=RL;");

                await writer.WriteLineAsync("        label = \"Legend\"");

                await writer.WriteLineAsync("        shape = rectangle;");

                await writer.WriteLineAsync("        color = black;");

                await writer.WriteLineAsync("        a[style = invis];");

                await writer.WriteLineAsync("        b[style = invis];");

                await writer.WriteLineAsync("        c[style = invis];");

                await writer.WriteLineAsync("        d[style = invis];");

                await writer.WriteLineAsync("        e[style = invis];");

                await writer.WriteLineAsync("        f[style = invis];");

                await writer.WriteLineAsync("        c->d[label = \"Updated Every Build\", style = bold];");

                await writer.WriteLineAsync("        a->b[label = \"Updated Every Day\", style = dashed];");

                await writer.WriteLineAsync("        e->f[label = \"Disabled/Updated On-demand\", style = dotted];");

                await writer.WriteLineAsync("    }");

                await writer.WriteLineAsync("}");
            }
        }