KBEC-00107 - Calculating and displaying an estimated job duration

Article ID:360033194171
4 minute readKnowledge base

Summary

CloudBees CD (CloudBees Flow) does not have a built-in feature to show an estimated job duration. However, it is easy to calculate by retrieving previous jobs, and it is easy to display by temporarily modifying the intrinsic "jobName" property of the Job.

Solution

Grouping Similar Jobs

The most important question is, "Which Jobs are similar in duration to the current Job?" This question must be answered for each installation of CloudBees CD (CloudBees Flow), because there is no set of universal information that CloudBees CD (CloudBees Flow) can use to determine that two jobs are similar. For example, not all Jobs that are launched by a Procedure named "Master Build" will have the same expected duration. That single Procedure may be used to launch the builds of two separate code lines. Or it may build both test and production builds, which may take vastly different lengths of time to run.

The "jobType" Custom Property

This solution introduces a custom property of the Job, named "jobType". It is computed in the Perl code in the "Setup" step below. To use this in your own Jobs, just add the "Setup" step below.

The "jobType" is an arbitrary string that is used to associate Jobs that are expected to run with the same duration. It can be as simple as the "procedureName" or it can be a complex, computed string, for example, combining the Procedure name with the values of the parameters and other properties.

In any case, you will have to customize the Perl code below to set the string to group Jobs that are similar.

Calculating the Estimated Duration

Once there is a "jobType" property that can be used to group similar Jobs, it is a simple matter, using CloudBees CD (CloudBees Flow)'s Perl API to find previous Jobs of the same type and average their actual durations to come up with an estimated duration for the current Job.

Displaying the Estimated Duration

The estimated duration is displayed by modifying the "jobName" intrinsic property to include the estimated duration in the job name. This will be displayed wherever the Job Name is shown - the Job Details page, the Jobs list, and so on.

The "Cleanup" step below restores the "jobName" back to its original state when the job completes. When the job is over, there is no value in displaying the estimated duration.

Steps to Add to Your Procedure

Create a step like this at or near the beginning of your main procedure. Be sure to customize the line that sets the "$jobType" variable so that it will correctly group Jobs of similar type in your environment.

Name: Setup
Shell: ec-perl
Command:

use strict;
use ElectricCommander;
$| = 1;
my $ec = new ElectricCommander;

    #  The job type is simply a label used for estimating durations.
    #  The duration will be calculated by averaging previous
    #   jobs with the same job type.
    #  Create a label that associates similar jobs
    #  It could be as simple as a procedure name, a procedure plus parameter,
    #  or something arbitrarily complex

    #  You should change the next line of code to create a label that
    #  will group similar jobs

    my $jobType = "$[/myProject/projectName]-$[/myJob/procedureName]-$[parameter1]";

    #  Save the jobtype so that future jobs can search for it
    print qq{Job Type is "$jobType"\n};
    $ec->setProperty("/jobs/$[jobId]/jobType", $jobType);

    # Get the actual run time from the last 10 jobs of the same "job type"
    #  Only completed, successful jobs will be included
    my @filterList;
    push (@filterList, {"propertyName" => "status",
                        "operator" => "equals",
                        "operand1" => "completed"});
    push (@filterList, {"propertyName" => "outcome",
                        "operator" => "equals",
                        "operand1" => "success"});
    push (@filterList, {"propertyName" => "jobType",
                        "operator" => "equals",
                        "operand1" => $jobType});

    #  Find the last 10 completed jobs of the same type
    my $xPath = $ec->findObjects("job",
                                 {maxIds        => "10",
                                  numObjects    => "10",
                                  filter        => \@filterList,
                                 });

    # Extract the elapsed time from each job
    my $nodeset = $xPath->find('//job');
    my $completedJobCount = 0;
    my $elapsedTimeSum = 0;
    foreach my $node ($nodeset->get_nodelist)
    {
        my $elapsedTime = $xPath->findvalue('elapsedTime', $node);
        # print  "Time is $elapsedTime\n";
        $completedJobCount++;
        $elapsedTimeSum += $elapsedTime;
    }

    #  Update the job name if there is an estimate
    if ($completedJobCount) {

        # Compute new job name
        my $averageElapsedTime = $elapsedTimeSum / $completedJobCount;
        my $formattedTime = formatTime($averageElapsedTime);
        my $originalJobName = "$[jobName]";
        my $addedPieceOfName = "  (Duration - $formattedTime)";
        my $newJobName = $originalJobName . $addedPieceOfName;

        # Set the new name
        print qq{Setting the job name to "$newJobName"\n};
        $ec->setJobName("$[jobId]", $newJobName);

        # Save the added piece so that the Cleanup step can remove it
        $ec->setProperty("/jobs/$[jobId]/addedPieceOfName", $addedPieceOfName);
    }



sub formatTime(@) {

    # convert milliseconds to seconds
    my $milliseconds = shift;
    my $seconds = int(($milliseconds/1000) + 0.5);

    #  Round to the nearest minute, with a minimum of one minute
    $seconds += 30;
    $seconds = 60 if ($seconds < 60);

    # convert to hours, minutes, seconds
    my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = gmtime($seconds);
    my $daysString = "";
    $daysString = "$yday day, " if ($yday == 1);
    $daysString = "$yday days, " if ($yday > 1);

    return $daysString . sprintf("%02d:%02d", $hour, $min);
}

At or near the end of your Procedure, you should add a Cleanup step that restores the original "jobName" using the following code. There is no need to customize this code.

Name: Cleanup
Shell: ec-perl
Always Run: checked
Command:

use strict;
use ElectricCommander;
$| = 1;
my $ec = new ElectricCommander;

    # Look for the property that defines what was added
    my $addedPieceOfName = "$[/javascript myJob.addedPieceOfName]";

    # Remove the added piece from the current name
    if ($addedPieceOfName) {
        # Get the current name
        my $jobName = "$[/myJob/jobName]";

        # Recreate the original name by deleting the added piece
        # "\Q" tells it to apply "quotemeta" so that meta characters
        #      will match as literal characters
        $jobName =~ s/\Q$addedPieceOfName//;
        $ec->setJobName("$[jobId]", $jobName) if ($jobName);
    }

Both of these steps could be made into separate Subprocedures in a library project and then called from your procedure using a Subprocedure Step.

Applies to

  • Product versions: CloudBees CD (CloudBees Flow) 3.0 and later

  • OS versions: All