Viewing all Queued Builds in TFS: Part 2

Viewing all Queued Builds in TFS: Part 2

As I discussed in part one of this article, TFS 2010 does not give a friendly view for an administrator to see what all builds are currently running. Because our TFS has so many team projects, it becomes extremely annoying to dig through each project to find out when a build has hung up a build agent.

Recently, we decided to retire our existing build machines and replace them with newer, more powerful machines, machines that would be capable of running more than one build at a time. Previously, we had several build machines (5-6), each of which had a single build controller and build agent running on them. Unfortunately, if six different developers on different development teams working in different Team Projects queued up a build on the same build machine, then five of the builds would be queued on that single machine, and none of those developers (or more importantly, me!) had an easy view into what they were waiting for. Ultimately, that one machine became wasteful bottleneck, as there would be several other unused machines.

Because of this, and other reasons, we revised our build machines’ topology. Now we are using a single build controller, and then we configured twelve new build agents spread across several machines. This allowed the build controller to make use of all the available build agents and solved the bottleneck caused by our prior topology.

Unfortunately, this made the console application that I wrote obsolete. Before, the console app only reported the build controllers that had builds running on them. This was fine, as long as there was a 1:1 relationship between build machines and build controllers. But now we only have one build controller, so if there was an issue with one of the twelve build agents assigned to that controller, the console app would not be useful to us as a troubleshooting tool.

To solve this problem, I further mangled/modified the console app posted on Bart Wullem’s blog to display the build controller’s name and the build agent’s name for all queued builds.

It is pretty simple to create a new console application in Visual Studio using the source I documented below. However, if you are anything like me you sure appreciate when someone shares a binary that they can use. You can download a copy here.

Installation Directions

  1. Unzip the contents to a folder. (Optional: For ease of use, add the folder you unzipped it to into your Windows PATH environment variable)
  2. Open the folder and edit the config file (TFSBuildQueue.exe.config), there is a single configuration entry in there for the URL of your TFS Project Collection (usually: http://yourtfsserver:8080/tfs/defaultcollection)
  3. Open a command console and set the width to at least 180 columns
  4. Change Directory to the path you unzipped TFSBuildQueue.zip to.
  5. Execute tfsbuildqueue.exe

Source Code

    using System;
    using System.Configuration;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Data;
    using Microsoft.TeamFoundation.Client;
    using Microsoft.TeamFoundation.Build.Client;
    
    namespace TFSBuildQueue
    {
        class Program
        {
            static void Main(string[] args)
            {
                int BuildCount = 0;
                string TFS_URL = ConfigurationManager.AppSettings["TFS_URL"];
                Console.WriteLine("\nTFS Build Queue");
                Console.WriteLine("===============\n");
                Console.WriteLine("Connecting to: " + TFS_URL + " and querying build controllers...");
                TfsTeamProjectCollection tfs = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new            Uri(TFS_URL));
                IBuildServer bs = tfs.GetService<IBuildServer>();
                IQueuedBuildSpec qbSpec = bs.CreateBuildQueueSpec("*", "*");
                IQueuedBuildQueryResult qbResults = bs.QueryQueuedBuilds(qbSpec);
    
    
                // Define DataTable for storage and manipulation of currently queued builds.
                DataTable QBTable = new DataTable();
                QBTable.Columns.Add("BuildMachine");
                QBTable.Columns.Add("Project");
                QBTable.Columns.Add("BuildDefinition");
                QBTable.Columns.Add("BuildStatus");
                QBTable.Columns.Add("Priority");
                QBTable.Columns.Add("Date");
                QBTable.Columns.Add("ElapsedTime");
                QBTable.Columns.Add("User");
    
    
                // Query TFS For Queued builds and write each build to QBTable
                foreach (IQueuedBuild qb in qbResults.QueuedBuilds)
                {
                    qb.Build.RefreshAllDetails();
                    string RequestedBy = qb.RequestedBy.PadRight(18);
                    if (qb.RequestedBy != qb.RequestedFor)
                    {
                        RequestedBy = String.Concat(qb.RequestedBy, " (for ", qb.RequestedFor, ")").PadRight(18);
                    }
                    DateTime CurrentTime = DateTime.Now;
                    TimeSpan ElapsedTime = CurrentTime.Subtract(qb.QueueTime);
                    string ElapsedTimeString = ElapsedTime.ToString();
                    String TFSET = ElapsedTimeString;
                    String TFS_TEAMPROJECT;
                    String BuildAgentStr;
                    String BuildMachineStr;
                    if (qb.Status.ToString() != "InProgress")
                    {
                        TFS_TEAMPROJECT = "-------";
                        BuildAgentStr = "N/A";
                    }
                    else
                    {
                        TFS_TEAMPROJECT = qb.Build.TeamProject;
                        BuildAgentStr = GetBuildAgent(qb.Build);
                    }
                    BuildMachineStr = qb.BuildController.Name + " (" + BuildAgentStr.ToUpper() + ")";
    
                    QBTable.Rows.Add(
                    BuildMachineStr.PadRight(46),
                    TFS_TEAMPROJECT.PadRight(17),
                    qb.BuildDefinition.Name.PadRight(28),
                    qb.Status.ToString().PadRight(11),
                    qb.Priority.ToString().PadRight(9),
                    qb.QueueTime.ToString().PadRight(23),
                    TFSET.PadRight(17),
                    RequestedBy.PadRight(19)
                    );
                 BuildCount++;
                }

                // Sorts QBTable on Build controller then by date
                DataRow[] QBSorted = QBTable.Select("", "BuildMachine ASC, Date ASC");

                // Writes the headers 
                WriteHeaders();

                foreach (DataRow dataRow in QBSorted)
                {
                    WriteReportLine(
                        dataRow[0].ToString(),
                        dataRow[1].ToString(),
                        dataRow[2].ToString(),
                        dataRow[3].ToString(),
                        dataRow[4].ToString(),
                        dataRow[5].ToString(),
                        dataRow[6].ToString(),
                        dataRow[7].ToString());
                }

    
                Console.WriteLine("\n\nTotal Builds Queued: " + BuildCount + "\n\n");
            }
    
            static void WriteHeaders()
            {
                Console.WriteLine("\n\n");
                Console.WriteLine("Build Controller (Agent)".PadRight(46) + " " +
                                  "Project".PadRight(17) + " " +
                                  "Build Definition".PadRight(28) + " " +
                                  "Status".PadRight(11) + " " +
                                  "Priority".PadRight(9) + " " +
                                  "Date & Time Started".PadRight(23) + " " +
                                  "Elapsed Time".PadRight(17) + " " +
                                  "User".PadRight(19));
                Console.WriteLine("============================================".PadRight(46) + " " +
                                  "=======".PadRight(17) + " " +
                                  "================".PadRight(28) + " " +
                                  "=======".PadRight(11) + " " +
                                  "========".PadRight(9) + " " +
                                  "=====================".PadRight(23) + " " +
                                  "================".PadRight(17) + " " +
                                  "============".PadRight(19));
            }
    
            static void WriteReportLine(string TFSBuildController, string TFSProject, string TFSBuildDefinition, string TFSBuildStatus, string TFSBuildPriority, string TFSBuildDateTime, string ElapsedTime, string TFSBuildUser)
            {
                Console.WriteLine("{0} {1} {2} {3} {4} {5} {6} {7}", TFSBuildController, TFSProject, TFSBuildDefinition, TFSBuildStatus, TFSBuildPriority, TFSBuildDateTime, ElapsedTime, TFSBuildUser);
            }
    
            public static string GetBuildAgent(IBuildDetail build)  //IQueuedBuild.Build
            {
                foreach (var child in build.Information.Nodes)
                {
                    string AgentName = ShowChild(child, 1);
                    if (!string.IsNullOrEmpty(AgentName))
                    {
                        return AgentName;
                    }
                }
                return string.Empty;
            }

            static string ShowChild(IBuildInformationNode node, int level)
            {
                string levelStr = new string(' ', level * 4);
                foreach (var field in node.Fields)
                {
                    if (field.Key == "ReservedAgentName")
                    {
                        return field.Value;
                    }
                }
    
                foreach (var child in node.Children.Nodes)
                {
                    string AgentName = ShowChild(child, level + 1);
                    if (!string.IsNullOrEmpty(AgentName))
                    {
                        return AgentName;
                    }
                }
                return string.Empty;
    
            }
    
        }
    
    }
Cookies
essential