I checked out the Jenkins source code, and I believe Jenkins uses the last 3 successful or unstable builds for its estimation. If it out of the last 6 builds it can't find 3 successful or unstable builds, it uses 1 or more of the last completed (not aborted) builds. It then takes the total duration time of the "candidates" and simply divides it by the number of candidates (i.e. mean of the durations). Here is the code I based this off of that can be found in the Job.java file in the Jenkins repo:
/**
* Returns candidate build for calculating the estimated duration of the current run.
*
* Returns the 3 last successful (stable or unstable) builds, if there are any.
* Failing to find 3 of those, it will return up to 3 last unsuccessful builds.
*
* In any case it will not go more than 6 builds into the past to avoid costly build loading.
*/
@SuppressWarnings("unchecked")
protected List<RunT> getEstimatedDurationCandidates() {
List<RunT> candidates = new ArrayList<RunT>(3);
RunT lastSuccessful = getLastSuccessfulBuild();
int lastSuccessfulNumber = -1;
if (lastSuccessful != null) {
candidates.add(lastSuccessful);
lastSuccessfulNumber = lastSuccessful.getNumber();
}
int i = 0;
RunT r = getLastBuild();
List<RunT> fallbackCandidates = new ArrayList<RunT>(3);
while (r != null && candidates.size() < 3 && i < 6) {
if (!r.isBuilding() && r.getResult() != null && r.getNumber() != lastSuccessfulNumber) {
Result result = r.getResult();
if (result.isBetterOrEqualTo(Result.UNSTABLE)) {
candidates.add(r);
} else if (result.isCompleteBuild()) {
fallbackCandidates.add(r);
}
}
i++;
r = r.getPreviousBuild();
}
while (candidates.size() < 3) {
if (fallbackCandidates.isEmpty())
break;
RunT run = fallbackCandidates.remove(0);
candidates.add(run);
}
return candidates;
}
public long getEstimatedDuration() {
List<RunT> builds = getEstimatedDurationCandidates();
if(builds.isEmpty()) return -1;
long totalDuration = 0;
for (RunT b : builds) {
totalDuration += b.getDuration();
}
if(totalDuration==0) return -1;
return Math.round((double)totalDuration / builds.size());
}