All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.netflix.control.clutch.ExperimentalControlLoop Maven / Gradle / Ivy

There is a newer version: 3.1.3
Show newest version
/*
 * Copyright 2020 Netflix, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.netflix.control.clutch;

import java.util.concurrent.atomic.AtomicLong;

import com.google.common.util.concurrent.AtomicDouble;
import com.netflix.control.IActuator;
import com.netflix.control.controllers.ErrorComputer;
import com.netflix.control.controllers.Integrator;
import com.netflix.control.controllers.PIDController;
import com.yahoo.sketches.quantiles.UpdateDoublesSketch;
import io.vavr.Tuple;
import lombok.extern.slf4j.Slf4j;
import rx.Observable;


@Slf4j
public class ExperimentalControlLoop implements Observable.Transformer {

    private final ClutchConfiguration config;
    private final IActuator actuator;
    private final Double initialSize;
    private final AtomicDouble dampener;
    private final long cooldownMillis;


    private final AtomicLong cooldownTimestamp;
    private final AtomicLong currentScale;
    private final Observable size;

    private final UpdateDoublesSketch sketch = UpdateDoublesSketch.builder().setK(1024).build();

    public ExperimentalControlLoop(ClutchConfiguration config, IActuator actuator, Double initialSize, Observable size) {
        this(config, actuator, initialSize, new AtomicDouble(1.0), size);
    }

    public ExperimentalControlLoop(ClutchConfiguration config, IActuator actuator, Double initialSize,
                                   AtomicDouble dampener, Observable size) {
        this.config = config;
        this.actuator = actuator;
        this.initialSize = initialSize;
        this.dampener = dampener;
        this.cooldownMillis = config.getCooldownUnits().toMillis(config.cooldownInterval);

        this.cooldownTimestamp = new AtomicLong(System.currentTimeMillis() + this.cooldownMillis);
        this.currentScale = new AtomicLong(Math.round(initialSize));
        this.size = size;

    }

    @Override
    public Observable call(Observable events) {
        events = events.share();

        Observable lag =
                Observable.just(new Event(Clutch.Metric.LAG, 0.0))
                        .mergeWith(events.filter(event -> event.getMetric() == Clutch.Metric.LAG));

        Observable drops =
                Observable.just(new Event(Clutch.Metric.DROPS, 0.0))
                        .mergeWith(events.filter(event -> event.getMetric() == Clutch.Metric.DROPS));

        Observable rps = events.filter(event -> event.getMetric() == Clutch.Metric.RPS);

        Integrator integrator = new Integrator(initialSize, config.minSize, config.maxSize);

        size.doOnNext(currentScale::set)
                .doOnNext(integrator::setSum)
                .doOnNext(n -> log.info("Clutch received new scheduling update with {} workers.", n))
                .subscribe();

        return rps.withLatestFrom(lag, drops, Tuple::of)
                .map(triple -> {
                    return triple._1.value // RPS
                            + (triple._2.value / this.currentScale.get())  // LAG per worker
                            + (triple._3.value / this.currentScale.get()); // DROPS per worker
                })
                .lift(new ErrorComputer(config.setPoint, true, config.rope._1, config.rope._2))
                .lift(PIDController.of(config.kp, config.ki, config.kd))
                .lift(integrator)
                .filter(__ -> this.cooldownMillis == 0 || cooldownTimestamp.get() <= System.currentTimeMillis() - this.cooldownMillis)
                .filter(scale -> this.currentScale.get() != Math.round(Math.ceil(scale)))
                .lift(actuator)
                .doOnNext(scale -> this.currentScale.set(Math.round(Math.ceil(scale))))
                .doOnNext(__ -> cooldownTimestamp.set(System.currentTimeMillis()));
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy