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

com.github.robozonky.app.tenant.QuotaMonitor Maven / Gradle / Ivy

/*
 * Copyright 2020 The RoboZonky Project
 *
 * 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.github.robozonky.app.tenant;

import java.time.Duration;
import java.util.Arrays;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicBoolean;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import com.github.robozonky.internal.remote.ApiProvider;
import com.github.robozonky.internal.remote.RequestCounter;

/**
 * Zonky implements a quota for how many operations a user can perform over their API during a given period of time.
 * This class measures a {@link RequestCounter} (possibly coming from {@link ApiProvider}) and issues warnings on the
 * use of that quota.
 */
final class QuotaMonitor implements Runnable {

    private static final Logger LOGGER = LogManager.getLogger(QuotaMonitor.class);
    private static final int[] THRESHOLDS_IN_PERCENT = new int[] { 50, 75, 90, 99 };
    private static final int LIMIT_PER_THROTTLING_INTERVAL = 3000;
    private static final Duration THROTTLING_INTERVAL = Duration.ofSeconds(3000);

    private final RequestCounter counter;
    private final SortedSet thresholdsReached = new TreeSet<>();

    public QuotaMonitor(final RequestCounter counter) {
        this.counter = counter;
    }

    public boolean threshold50PercentReached() {
        return thresholdReached(50);
    }

    public boolean threshold75PercentReached() {
        return thresholdReached(75);
    }

    public boolean threshold90PercentReached() {
        return thresholdReached(90);
    }

    public boolean threshold99PercentReached() {
        return thresholdReached(99);
    }

    private boolean thresholdReached(final int percentage) {
        return thresholdsReached.contains(percentage);
    }

    @Override
    public void run() {
        final int requestsInLast600Seconds = counter.count(THROTTLING_INTERVAL);
        final AtomicBoolean informedOfReturnToNormal = new AtomicBoolean();
        LOGGER.debug("Requests in {}: {}.", THROTTLING_INTERVAL, requestsInLast600Seconds);
        Arrays.stream(THRESHOLDS_IN_PERCENT)
            .forEach(thresholdInPercent -> {
                final double actualThreshold = (LIMIT_PER_THROTTLING_INTERVAL * thresholdInPercent) / 100.0;
                final boolean thresholdReachedBefore = thresholdReached(thresholdInPercent);
                if (requestsInLast600Seconds < actualThreshold) { // OK
                    if (thresholdReachedBefore) { // was not OK before, inform
                        thresholdsReached.remove(thresholdInPercent);
                        if (informedOfReturnToNormal.getAndSet(true)) {
                            // thresholds processed from low to high; once lower done, no need to inform of any higher
                            LOGGER.info("Request quota estimated back below {} % ({}).", thresholdInPercent,
                                    requestsInLast600Seconds);
                        }
                    }
                } else { // not OK
                    if (!thresholdReachedBefore) { // newly over threshold, inform
                        thresholdsReached.add(thresholdInPercent);
                        if (thresholdInPercent > 90) {
                            LOGGER.warn("Request quota estimated over {} % ({}), throttling imminent.",
                                    thresholdInPercent,
                                    requestsInLast600Seconds);
                        } else {
                            LOGGER.info("Request quota estimated over {} % ({}).", thresholdInPercent,
                                    requestsInLast600Seconds);
                        }
                    }
                }
            });
        counter.keepOnly(THROTTLING_INTERVAL.multipliedBy(2)); // clean up a bit
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy