public synchronized double percentFinished() {
        long superTotal = totalBucketCount;
        long superFinished = finishedBucketCount;

        if (superTotal == 0 || superTotal == superFinished) return 100;

        double superDelta = 100.0 / superTotal;
        double cumulativeSubProgress = 0;

        // Calculate cumulative for all non-finished buckets. 0 means the
        // bucket has yet to see any progress
        // There are numerical precision issues here, but this hardly requires
        // aerospace engineering result-accuracy
        for (Map.Entry<BucketKeyWrapper, ProgressToken.BucketEntry> entry : buckets.entrySet()) {
            BucketId superbucket = new BucketId(keyToBucketId(entry.getKey().key()));
            BucketId progress = entry.getValue().getProgress();
            // Prevent calculation of bucket progress on inconsistent buckets
            if (progress.getId() != 0 && superbucket.contains(progress)) {
                cumulativeSubProgress += superDelta * progressFraction(superbucket, progress);
            }
        }

        return (((double)superFinished / (double)superTotal) * 100.0)
                + cumulativeSubProgress;
    }
Calculate an estimate on how far we've managed to iterate over both the
superbuckets and the sub-buckets.

Runs in <em>O(n+m)</em> time, where <em>n</em> is the number of active buckets
and <em>m</em> is the number of pending buckets. Both these values should
be fairly small in practice, however.

Method is synchronized, as legacy code treats this as an atomic read.

@return A value in the range [0, 100] estimating the progress.
documentapi/src/main/java/com/yahoo/documentapi/ProgressToken.java