private void buildLoadBalancer() {
        final ImmutableList.Builder<Weighted> targetCandidatesBuilder = ImmutableList.builder();
        targetCandidatesBuilder.addAll(candidatesFinishedRampingUp);
        for (CandidatesRampingUpEntry<T> entry : rampingUpWindowsMap.values()) {
            for (CandidateAndStep<T> candidateAndStep : entry.candidateAndSteps()) {
                targetCandidatesBuilder.add(
                        // Capture the current weight of the candidate for the current step.
                        new WeightedObject<>(candidateAndStep.candidate(), candidateAndStep.currentWeight()));
            }
        }
        final List<Weighted> candidates = targetCandidatesBuilder.build();
        if (rampingUpWindowsMap.isEmpty()) {
            logger.info("Finished ramping up. candidates: {}", candidates);
        } else {
            logger.debug("Ramping up. candidates: {}", candidates);
        }

        boolean found = false;
        for (Weighted candidate : candidates) {
            if (candidate.weight() > 0) {
                found = true;
                break;
            }
        }
        if (!found) {
            logger.warn("No valid candidate with weight > 0. candidates: {}", candidates);
        }
        weightedRandomLoadBalancer = LoadBalancer.ofWeightedRandom(candidates);
    }
A ramping up {@link LoadBalancer} which ramps the weight of newly added
candidates using {@link WeightTransition}, {@code rampingUpIntervalMillis} and {@code rampingUpTaskWindow}.
If more than one candidate are added within the {@code rampingUpTaskWindow}, the weights of
them are updated together. If there's already a scheduled job and new candidates are added
within the {@code rampingUpTaskWindow}, they are updated together.
This is an example of how it works when {@code rampingUpTaskWindow} is 500 milliseconds and
{@code rampingUpIntervalMillis} is 2000 milliseconds:
<pre>{@code
----------------------------------------------------------------------------------------------------
    A         B                             C                                       D
    t0        t1                            t2                                      t3         t4
----------------------------------------------------------------------------------------------------
    0ms       t0 + 200ms                    t0 + 1000ms                          t0 + 1800ms  t0 + 2000ms
}</pre>
A and B are ramped up right away when they are added and they are ramped up together at t4.
C is updated alone every 2000 milliseconds. D is ramped up together with A and B at t4.
core/src/main/java/com/linecorp/armeria/common/loadbalancer/RampingUpLoadBalancer.java